Compare commits

...

155 Commits

Author SHA1 Message Date
Igor Minar d3d2a3a374 cutting the 0.9.9 time-shift release 2011-01-13 22:08:27 -08:00
Igor Minar 527d0a1600 updating release notes 2011-01-13 22:06:28 -08:00
Igor Minar 23875cb330 significant rewrite of the $location service
- don't update browser before and after eval instead
  - sync location properties before eval
  - sync location properties and update browser after eval
- added tests
- symplified the code
- removed $location.toString() because it was not idempotent and useless

This resolves the issue with issuing two $route.onHashChange calls
when the $location was updated with a hashPath that needs to be encoded
2011-01-13 16:58:35 -08:00
Igor Minar b0be87f663 fix for angular.Object.equals to handle equals({},null) 2011-01-13 07:55:31 -08:00
Igor Minar 9ccd2f0412 adding a fallthrough comment 2011-01-11 17:48:30 -08:00
Igor Minar 99004b0aed fixing missing dependency 2011-01-10 23:28:47 -08:00
Igor Minar ab040254f0 tabbing and keyboard shortcut for docs
- first tab goes to the search box, following tabs iterate results
- ctrl+s (FF on mac) or ctrl+alt+s (Chrome on mac) move focus to the
  search box
2011-01-10 21:23:22 -08:00
Igor Minar 4f5d5029c2 offline docs
- freezing syntaxhighlighter and jquery under docs/src/templates/
- for jquery I just used a symlink to lib/jquery not an ideal solution
  but writer.js is not very flexible and I didn't want to mess with it
- changed docs' index.html to point to the local resources
2011-01-10 21:03:50 -08:00
Misko Hevery f534def0c6 removed unnedded files; switch rake to new doc build script 2011-01-10 14:48:13 -08:00
Misko Hevery c3e32f1a51 fix broken e2e test due to $window no longer published 2011-01-10 13:55:08 -08:00
Misko Hevery 4f22d6866c complete rewrite of documentation generation
- romeved mustache.js
- unified templates
- improved testability of the code
2011-01-10 11:50:11 -08:00
Vojta Jina aab3df7aea Fixed filter.date, so it parses format ending with non-replaced string
Closes #159
2011-01-10 10:37:37 -08:00
Igor Minar 0a6cf70deb Rename angular.foreach to angular.forEach to make the api consistent.
camelcase is used for other angular functions and forEach is also
used by EcmaScript standard.

- rename the internal as well as the external function name
- tweak the implementation of the function so that it doesn't
  clober it self when we extend the angular object with an
  object that has a forEach property equal to this forEach function

Closes #85
2011-01-10 10:26:55 -08:00
Igor Minar c79aba92f6 bootstrap angular on "document ready" instead of window.onload
- use jqLite api to bootstrap angular
- when jQuery is present DOMContentLoaded or hacks for IE are used
- when jqLite is present DOMContentLoaded is used for modern browsers
  and IE9 and window.onload is used for other browsers.
- test html for comparing DOMContentLoaded with window.onload

Closes #224
2011-01-10 10:24:12 -08:00
Igor Minar 84dedb81e7 jqLite should recognize window as an element even in IE
in IE window object has length property which makes it look like a
collection to jqLite. This commit makes jqLite properly identify
window as an element even in IE.

IE6 doesn't have Window type, so we need to check against window
object and only then do a more general check against Window. This
is not perfect, but I say screw IE6.
2011-01-10 10:24:12 -08:00
Igor Minar e999740044 fix addEventLister detection for IE9
IE9 added support for addEventListener and it looks like some
new events (e.g. DOMContentLoaded) can't be registered via attachEvent,
only addEventListener correctly registeres these. So to make sure
we are using the latest and greatest we need to look for addEventListener
instead of attachEvent in the jqLite feature detection code.
2011-01-10 10:24:12 -08:00
Igor Minar 0ad39dde4f fix onhashchange tests for IE 2011-01-10 10:24:12 -08:00
Misko Hevery 4c71824a69 fix for IE free function weirdness 2011-01-10 10:22:24 -08:00
Misko Hevery 47c454a315 change to keydown from keyup; add delayed $updateView
- There was a perceived lag when typing do to the fact that we were
   listening on the keyup event instead of keydown. The issue with
   keydown is that we can not read the value of the input field. To
   solve this we schedule a defer call and perform the model update
   then.

 - To prevent calling $eval on root scope too many times as well as to
   prevent drowning the browser with too many updates we now call the
   $eval only after 25ms and any additional requests get ignored. The
   new update service is called $updateView
2011-01-07 14:39:46 -08:00
Igor Minar 16086aa37c $location service should utilize onhashchange events instead of polling 2011-01-07 14:39:41 -08:00
Igor Minar c0a26b1853 jasmine adapter with better stack traces 2011-01-07 12:02:46 -08:00
Igor Minar 9db2170dcf add personalLog files to jsTestDriver-jquery.conf 2011-01-07 12:02:46 -08:00
Igor Minar b28dee7fd5 use toMatch in widgetSpec 2011-01-07 12:02:46 -08:00
Igor Minar 142a985f33 Revert "Issue #51: Update extensionMap()"
This reverts commit 00ca67e4be.

Now that we don't have published services, we don't need to worryi
about this any more and in fact this behavior is confusing because
to override a service that has dependencies with a services with
no dependencies one must specify $inject:[] or else the old
dependencies will be injected.

Conflicts:

	src/Angular.js
	test/AngularSpec.js
2011-01-07 12:02:45 -08:00
Igor Minar bd5ec7c32a rename nodeName due to conflicts with jquery 2011-01-07 12:02:45 -08:00
Igor Minar bdc251c5a5 add global namespace clobbering check to angular-bootstrap 2011-01-07 12:02:45 -08:00
Igor Minar ad9537cdf6 remove unused jquery ui lib 2011-01-07 12:02:45 -08:00
Misko Hevery 807d8c92b3 removed dissabled tests which are now outdated 2011-01-06 14:51:44 -08:00
Misko Hevery 454626ad39 converted last of tests to specs 2011-01-06 14:34:21 -08:00
Misko Hevery 247c99a8a4 upgrade to latest jasmine adapter 2011-01-06 14:22:45 -08:00
Igor Minar da1d50fbe9 avoid namespace conflicts between jqLite and window when running in bootstrap mode 2011-01-04 18:04:01 -08:00
Igor Minar 67d064820c fixing up personalLog app 2011-01-04 18:04:01 -08:00
Igor Minar b2631f6170 rename scope.$inject to scope.$service
see changelog diff for more info
2011-01-04 18:04:00 -08:00
Igor Minar 1430c6d6b1 renaming service property $creation to $eager
see changelog diff for more info
2011-01-04 18:03:55 -08:00
Igor Minar 3ea5941f0e removing support for 'eager-published' services 2011-01-04 16:40:41 -08:00
Misko Hevery d0270d9256 Remove many eager-publish services, lazy polling
- Browser now starts the poller on first call to addPollFn()

- Many services ($location, $cookies, $router) are no longer eager-publish. The result is that
  unless someone needs the $cookies, they will not cause the Browser to start polling for them.
2011-01-04 16:40:40 -08:00
Misko Hevery 5f080193cb Added an extra security check to make sure that the parser can not execute anything while evaluting JSON. 2011-01-04 13:25:49 -08:00
Igor Minar cf891428bf upgrading JSTD to r788 2010-12-28 17:32:20 -08:00
Igor Minar 5b9967518e preparing the 0.9.9 time-shift iteration 2010-12-23 22:43:42 -08:00
Igor Minar 38f462d572 cutting the 0.9.8 astral-projection release 2010-12-23 22:42:11 -08:00
Igor Minar 56eeba0f3c release notes for the 0.9.8 release 2010-12-23 22:41:05 -08:00
Igor Minar 5a534235b6 fixing invalid json strings in ResourceSpec 2010-12-23 09:03:34 -08:00
Igor Minar e7a0fb250f fromJson delegation to native JSON parser if available
- native parser delegation
- $xhr change to use native parser
2010-12-22 14:16:36 -08:00
Igor Minar e3ddc2bcc4 simple perf testing harness with JSON parsing tests
- all tests should be under perf/
- all payloads should be under perf/data
- run tests with ./server.sh + ./test-perf.sh

We still lack a way to compare results against a baseline, but
this is better than nothing.
2010-12-22 14:16:36 -08:00
Igor Minar d11088eb43 Revert "Refactor lexer to use regular expressions"
We came across a major performance regression due to this change.
I'm reverting it until we find a better solution.

This reverts commit 23fc73081f.
2010-12-22 14:16:36 -08:00
Misko Hevery a5df1fc41f Stricter JSON parsing, for security 2010-12-11 10:07:10 -08:00
Misko Hevery ec4d446f89 Closes #153: input widgets without name are ignored 2010-12-11 08:28:11 -08:00
Misko Hevery b225083a21 Fire $eval only if the value has actually changed on input 2010-12-11 08:27:52 -08:00
Igor Minar e84d3334b0 preparing the 0.9.8 astral-projection iteration 2010-12-10 22:16:10 -08:00
Igor Minar 7989c7d24a cutting the 0.9.7 sonic-scream release 2010-12-10 17:08:52 -08:00
Igor Minar 5c36f466e1 fixing release notes 2010-12-10 17:08:10 -08:00
Igor Minar f8151afd90 improve doc app scrollbars 2010-12-10 17:04:56 -08:00
Igor Minar 74120eaa0f updating release notes 2010-12-10 13:49:03 -08:00
Igor Minar b370fac4fc $defer service should always call $eval after callback finished
Closes #189
2010-12-10 13:22:44 -08:00
Misko Hevery 23fc73081f Refactor lexer to use regular expressions 2010-12-08 14:39:22 -08:00
Misko Hevery e5e69d9b90 Remove RegExp parser
- RegExp parser is rearly used, feature, and one should not have RegExps
  in views anyways, so we are removing it

BACKWARD INCOMPATIBLE CHANGE!!!
2010-12-08 14:36:51 -08:00
Misko Hevery fa722447f8 Fixed failed assignments of form abj[0].name=value
Closes #169
2010-12-08 14:20:26 -08:00
Igor Minar 81d10e819e make the docs angular 'logo' link filename agnostic 2010-12-07 20:43:10 -08:00
Igor Minar 809ca94e1c @returns tag should allow the content to be split into multiple lines 2010-12-07 16:07:14 -08:00
Igor Minar 824eab9029 improving $resource docs 2010-12-07 16:06:31 -08:00
Igor Minar d503dfe99b preparations for the 0.9.7 sonic-scream iteration 2010-12-06 21:24:49 -08:00
Igor Minar e4d33917e3 cutting the 0.9.6 night-vision release 2010-12-06 21:11:10 -08:00
Igor Minar 6326e2028b fixing release notes 2010-12-06 21:10:38 -08:00
Igor Minar 8aed2047f0 fixing closure compiler warnings 2010-12-06 20:48:47 -08:00
Igor Minar f60b6b0938 fix the angular.Array.orderBy example + add docs 2010-12-06 20:32:37 -08:00
Igor Minar eea7113abe updating release notes for the 0.9.6 release 2010-12-06 20:23:06 -08:00
Igor Minar c8f34e7f6b fixing lint warnings in older code 2010-12-06 16:45:59 -08:00
Igor Minar 011fa39c2a add $browser.defer and $defer service and fix async xhr cache issue
- Closes #152 ($resource().query() sometimes calls callback before
  returning, and it shouldn't)
- add $browser.defer method
- add $defer service
- integrate $browser.defer with outstandingRequests counter in $browser
- fix all old tests that relied on buggy behavior
2010-12-06 16:45:59 -08:00
Misko Hevery 58d0e8945d allow documentation to be in external file
* Load templates once instead of per request
* show timing information
* load files ending in .ngdoc and process them
2010-12-06 15:48:40 -08:00
Misko Hevery 2bbced212e Fix sanitization issues as suggested by evn 2010-12-03 15:42:42 -08:00
Misko Hevery 5a8ad8fe32 Closes #170. Corrected the behavior of select when options are ng:repeated
- Delete $postEval method, as it was a hack
2010-12-02 22:45:57 -08:00
Misko Hevery 41d5938883 Fixed sanitization
* explicitly require full URLs (ftp|https?://...)
* list the URI attributes
* remove a lot of unneeded attributes
2010-11-29 21:55:32 -08:00
Igor Minar 5480d2a80b fix typo in searchbox placeholder 2010-11-29 16:47:21 -08:00
Igor Minar 95adf3a4d8 searchbar in docs should not scroll away 2010-11-29 16:47:20 -08:00
Igor Minar cc315ef4cc renaming #section to #main in docs html/css 2010-11-29 16:47:20 -08:00
Igor Minar 41c233ada1 getting rid of #page div in docs index.html 2010-11-29 16:47:19 -08:00
Igor Minar 46091f811b dual scrolling for docs with custom scrollbars 2010-11-29 16:47:13 -08:00
Igor Minar fde2ccb3f5 docs html/css cleanup 2010-11-29 11:25:38 -08:00
Igor Minar 1cc2ad2443 adding bash header to server.sh 2010-11-29 08:59:35 -08:00
Igor Minar 1aa46fac62 preparing the 0.9.6 night-vision iteration 2010-11-29 08:58:20 -08:00
Igor Minar 5bde02a8ca cutting the 0.9.5 turkey-blast release 2010-11-25 10:11:26 -08:00
Igor Minar d07e9f77f1 updating the release notes for 0.9.5 2010-11-25 10:10:54 -08:00
Igor Minar aa21c521eb more docs for angular.service. 2010-11-25 09:50:07 -08:00
Vojta Jina bd14a90610 Doc specs: added test for markdown
It should replace markdown between two <pre></pre>***<pre></pre>
2010-11-25 08:51:32 -08:00
Vojta Jina 9f1b9849d8 Small refactor in doc parser 2010-11-25 08:51:32 -08:00
Vojta Jina 47f159cdf3 Doc service: added example into service overview 2010-11-25 08:51:26 -08:00
Igor Minar 99eb123d79 docs for all angular.* type checking functions 2010-11-25 08:19:14 -08:00
Igor Minar 6515adc118 docs for angular.injector 2010-11-24 22:50:34 -08:00
Igor Minar b7aff92354 docs for angular.bind 2010-11-24 22:33:40 -08:00
Igor Minar 6b3b00e095 docs for angular.foreach 2010-11-24 21:13:58 -08:00
Igor Minar 921f7ce49e docs for angular.extend 2010-11-24 21:03:56 -08:00
Igor Minar 17eee57c52 fixing docs for angular.scope 2010-11-24 19:29:45 -08:00
Igor Minar 4fc3ee8040 docs for angular.compile 2010-11-24 19:14:34 -08:00
Igor Minar 39d3ae80d9 docs for angular.element 2010-11-24 19:03:06 -08:00
Igor Minar 480f2f33c1 docs for angular.noop and angular.identity 2010-11-24 18:23:21 -08:00
Igor Minar 9c9a89f7ff docs for angular.scope. and angular.scope. 2010-11-24 18:07:11 -08:00
Igor Minar 73194009a9 docs for angular.Object.copy 2010-11-24 17:32:04 -08:00
Igor Minar 162f41a1ab docs for angular.Object.size 2010-11-24 17:21:37 -08:00
Igor Minar 7c82c4f837 docs for angular.Object and angular.Object.equals 2010-11-24 16:55:44 -08:00
Igor Minar 97b1371199 adding Note section to angular.Array.* where it was missing 2010-11-24 16:32:03 -08:00
Igor Minar 95d1768c77 docs for angular.Array.sum 2010-11-24 16:28:17 -08:00
Igor Minar c3d99d68da docs for angular.Array.filter 2010-11-23 17:14:04 -08:00
Igor Minar 303a683081 docs for angular.Array.remove 2010-11-23 16:28:24 -08:00
Igor Minar a0e8c45880 docs for angular.Array.remove 2010-11-23 16:10:14 -08:00
Igor Minar 870547d185 docs for angular.Array.orderBy 2010-11-23 15:44:14 -08:00
Igor Minar 0d1f8a0532 docs for angular.Array.count 2010-11-23 14:10:10 -08:00
Igor Minar b94600d71e adding docs for angular.Array.add 2010-11-23 11:05:28 -08:00
Igor Minar 3e5a4ef86c placeholder docs for all angular.Array functions 2010-11-22 10:57:44 -08:00
Igor Minar efec0c358d Add angular.Array.limitTo and docs for angular.Array 2010-11-22 10:57:32 -08:00
Igor Minar 1f59de35c9 preparation for the 0.9.5 turkey-blast iteration 2010-11-18 23:42:57 -08:00
Igor Minar 9b53b25f15 cutting the 0.9.4 total-recall release 2010-11-18 22:40:01 -08:00
Igor Minar 3fbfa357ca updated release notes for the 0.9.4 total-recall release 2010-11-18 22:40:00 -08:00
Igor Minar 50ef1f8e35 don't escape $ in hashpath either 2010-11-18 22:40:00 -08:00
Igor Minar 66c0bfaa8e don't escape ! and : in hashPath
This is a temporary fix for Issue #158
2010-11-18 20:51:31 -08:00
Igor Minar 1719b0aca5 fix all closure compilation warnings due to invalid function types 2010-11-18 17:03:43 -08:00
Igor Minar 7ee102eecf add a feedback link to the doc pages 2010-11-18 16:53:11 -08:00
Igor Minar fc7f11d03b add @workInProgress tag and mark all @ngdocs as work in progress 2010-11-18 16:28:42 -08:00
Igor Minar 3c7874b07b don't encode page.name (hash) in docs.js
see discussion in https://github.com/angular/angular.js/pull/158"
2010-11-18 11:56:10 -08:00
Igor Minar 7f339a1782 escape code in ng:bind-attr jsdoc 2010-11-18 11:33:09 -08:00
Igor Minar 72a5f007d8 most of the documentation for angular.scope and friends 2010-11-18 02:35:30 -08:00
Igor Minar 63380bbbda title for index.html should not show raw binding while the app bootstraps 2010-11-18 02:35:30 -08:00
Igor Minar c635b69f5c fix docs and examples for ng:format, ng:required and ng:validate 2010-11-18 02:35:30 -08:00
Igor Minar 522ec1a9ec move attribute widgets to widgets.js file
- move @ng:repeat to widgets.js and its specs to widgetsSpecs.js
- move @ng:non-bindable to widgets.js and its specs to widgetsSpecs.js
- make widget.template suitable for attribute widgets
- fix up the js docs for attribute widgets
2010-11-18 02:35:29 -08:00
Igor Minar 9cb57772a4 fix docs for angular.directive and ng:autobind 2010-11-18 02:35:29 -08:00
Igor Minar d54f09ef29 add spec for incrementing headings 2010-11-18 02:35:29 -08:00
Igor Minar 65989c6f0d add support for {@link} tags within @description and remove implicit linking
use as:
- foo {@link bar}
- foo {@link bar desc}

I'm removing implicit linking because it unintentionally links stuff and
generally interferes with other conversions. We have to link stuff explicitely
from now on.
2010-11-18 02:35:29 -08:00
Igor Minar 4491bbdede docs linkifying regexp should not mess up links alreaded converete by markdown 2010-11-18 02:35:28 -08:00
Igor Minar a6978b201b make @param type and description non-optional 2010-11-18 02:35:28 -08:00
Igor Minar 28e72cbe6b CSS, Parameters, Returns template changes
- make css section optional
- make returns section optional
- change format of the parameters section
- properly format the Returns section
2010-11-18 02:34:55 -08:00
Igor Minar 916dadd8ec adjust spacing of headings in the main doc div 2010-11-18 02:34:54 -08:00
Igor Minar e509ec37f5 fixing angular.lowercase and angular.upppercase jsdocs + api 2010-11-18 02:34:54 -08:00
Igor Minar ee0e9a4452 adding support for @param.optional 2010-11-18 02:34:54 -08:00
Igor Minar 9d36368ff9 fixing angular.filter.number jsdocs 2010-11-18 02:34:54 -08:00
Igor Minar d4bcee0799 toJson and fromJson jsdocs 2010-11-18 02:34:54 -08:00
Igor Minar dd687e2bf5 @returns description should support markdown 2010-11-18 02:34:53 -08:00
Igor Minar 4c69d694d7 make @returns type non-optional 2010-11-18 02:34:53 -08:00
Igor Minar ff7c738c21 fix ng docs for angular, angular.lowercase and uppercase 2010-11-18 02:34:53 -08:00
Igor Minar 51a22cf435 group utility methods/objects while sorting stuff for the side bar 2010-11-18 02:34:53 -08:00
Igor Minar c2c60ab49a remove extra returns tag 2010-11-18 02:34:53 -08:00
Igor Minar 71c2f24fc6 remove extra toolbar directive and support multiple pre's in describe 2010-11-18 02:34:52 -08:00
Igor Minar fc78738cc6 scope docs + lowercase doc fix 2010-11-18 02:34:52 -08:00
Igor Minar c7052f098d add support for @deprecated ng:doc annotation + show warnings in templates 2010-11-18 02:34:31 -08:00
Igor Minar 7d6f5f986e add function.template 2010-11-18 02:34:30 -08:00
Igor Minar beeb5ff908 fix regexp for @param parsing in ng:docs
There is an extra + in the regexp which causes exponential increase in time
needed to parse a @param annotation when the length of default value increases
linearly.
2010-11-16 14:44:35 -08:00
Misko Hevery b2d63ac48b Changed error handling so that better stack traces are displayed in the ng-errors 2010-11-16 14:19:55 -08:00
Igor Minar 4af32de84a docs should use ng:include onload 2010-11-16 13:45:45 -08:00
Igor Minar a130bb899d add onload attribute to ng:include 2010-11-16 11:35:43 -08:00
Vojta Jina cc749760fd Added basic Services, which support @memberOf and @methodOf 2010-11-15 21:55:37 -08:00
Misko Hevery b467a50bc7 generate keyword for searches; improved layout of doc 2010-11-15 12:28:08 -08:00
Misko Hevery a1652057a5 changed to ! notation for indexable apps 2010-11-15 10:04:17 -08:00
Misko Hevery 7e6f999221 added remaining directives and search box. 2010-11-15 10:04:17 -08:00
Igor Minar 625cc7609c fix code name in the change log 2010-11-12 16:18:11 -08:00
Igor Minar c51273b1fb Add test coverage analysis.
- jstd upgrade to head from 2010-11-11
- coverage plugin from the same head as jstd
- test-coverage.sh and server-coverage.sh scripts
- jstd configuration

Generate html by installing lconv (brew or port) and run:

genhtml tmp/lcov/jsTestDriver.conf-coverage.dat

to generate html docs.
2010-11-12 16:13:53 -08:00
Igor Minar 0a8b3161b1 $watch should optionally skip listener exec
- if initRun param is set to false, listener doesn't execute
- the oldValue should equal newValue during the initial execution
- added docs
- added specs
2010-11-11 16:39:01 -08:00
Igor Minar ba554eeb1b preparations for 0.9.4 total-recall iteration 2010-11-11 16:38:23 -08:00
137 changed files with 8836 additions and 3849 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<?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="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1262905463390_2&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/test&quot; type=&quot;2&quot;/&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<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 -1
View File
@@ -2,7 +2,7 @@
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/build&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs&quot; type=&quot;2&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<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,"/>
+2 -1
View File
@@ -2,5 +2,6 @@ build/
angularjs.netrc
jstd.log
.DS_Store
regression/temp.html
regression/temp*.html
performance/temp*.html
.idea/workspace.xml
+157
View File
@@ -1,3 +1,160 @@
# <angular/> 0.9.9 time-shift (2011-01-13) #
### Security
- Added a just in case security check for JSON parsing. (commit 5f080193)
- Completed security review with the Google Security Team.
### Performance
- $location and $cookies services are now lazily initialized to avoid the polling overhead when
not needed.
- $location service now listens for `onhashchange` events (if supported by browser) instead of
constant polling. (commit 16086aa3)
- input widgets known listens on keydown events instead of keyup which improves perceived
performance (commit 47c454a3)
- angular boots significantly sooner by listening for DOMContentLoaded event instead of
window.load when supported by browser (commit c79aba92)
- new service $updateView which may be used in favor of $root.$eval() to run a complete eval on
the entire document. This service bulks and throttles DOM updates to improve performance.
(commit 47c454a3)
### Docs
- Major improvements to the doc parser (commit 4f22d686)
- Docs now offline enabled (all dependencies are bundled in the tarball) (commit 4f5d5029)
- Added support for navigating the docs app with keyboard shortcuts (tab and ctrl+alt+s)
### Bugfixes
- `angular.Object.equals` now properly handless comparing an object with a null (commit b0be87f6)
- Several issues were addressed in the `$location` service (commit 23875cb3)
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
### Breaking changes
- API for accessing registered services — `scope.$inject` — was renamed to
[`scope.$service`](http://docs.angularjs.org/#!angular.scope.$service). (commit b2631f61)
- Support for `eager-published` services was removed. This change was done to make explicit
dependency declaration always required in order to allow making relatively expensive services
lazily initialized (e.g. $cookie, $location), as well as remove 'magic' and reduce unnecessary
scope namespace pollution. (commit 3ea5941f)
Complete list of affected services:
- $location
- $route
- $cookies
- $window
- $document
- $exceptionHandler
- $invalidWidgets
To temporarily preserve the 'eager-published' status for these services, you may use `ng:init`
(e.g. `ng:init="$location = $service('$location'), ...`) in the view or more correctly create
a service like this:
angular.service('published-svc-shim', function() {
this.$location = this.$service('$location');
this.$route = this.$service('$route');
this.$cookies = this.$service('$cookies');
this.$window = this.$service('$window');
this.$document = this.$service('$document');
this.$exceptionHandler = this.$service('$exceptionHandler');
this.$invalidWidgets = this.$service('$invalidWidgets');
}, {$eager: true});
- In the light of the `eager-published` change, to complete the cleanup we renamed `$creation`
property of services to `eager` with its value being a boolean.
To transition, please rename all `$creation: 'eager'` declarations to `$eager: true`.
(commit 1430c6d6)
- `angular.foreach` was renamed to `angular.forEach` to make the api consistent. (commit 0a6cf70d)
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
# <angular/> 0.9.8 astral-projection (2010-12-23) #
### Docs/Getting started
- angular-seed project to get you hacking on an angular apps quickly
https://github.com/angular/angular-seed
### Performance
- Delegate JSON parsing to native parser (JSON.parse) if available
### Bug Fixes
- Ignore input widgets which have no name (issue #153)
# <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
+1 -1
View File
@@ -158,7 +158,7 @@ end
desc 'Generate docs'
task :docs do
`node docs/collect.js`
`node docs/src/gen-docs.js`
end
+1
View File
@@ -8,6 +8,7 @@
border: 2px solid #FF0000;
font-family: "Courier New", Courier, monospace;
font-size: smaller;
white-space: pre;
}
.ng-validation-error {
+53
View File
@@ -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.
+43
View File
@@ -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.
+76
View File
@@ -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"}}
+78
View File
@@ -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');
});
+4
View File
@@ -0,0 +1,4 @@
@workInProgress
@ngdoc overview
@name angular
@namespace The exported angular namespace.
+159
View File
@@ -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>
+73
View File
@@ -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: &lt;input type="text" name="number" ng:validate="integer" value="123"&gt;
</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/);
});
+73
View File
@@ -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>
&lt;my:watch exp="name"/&gt;
</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>
&lt;div my-watch="name"&gt;text&lt;/div&gt;
</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>
-266
View File
@@ -1,266 +0,0 @@
require.paths.push("./lib");
require.paths.push(__dirname);
var fs = require('fs'),
spawn = require('child_process').spawn,
mustache = require('mustache'),
callback = require('callback'),
Showdown = require('showdown').Showdown;
var documentation = {
section:{},
all:[]
};
var SRC_DIR = "docs/";
var OUTPUT_DIR = "build/docs/";
var NEW_LINE = /\n\r?/;
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());
}
}));
}));
}));
}).onError(function(err){
console.log('ERROR:', err.stack || err);
}).onDone(function(){
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain());
mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain());
copy('docs-scenario.html', callback.chain());
copy('index.html', callback.chain());
mergeTemplate('docs.js', 'docs.js', documentation, callback.chain());
mergeTemplate('doc_widgets.css', 'doc_widgets.css', documentation, callback.chain());
mergeTemplate('doc_widgets.js', 'doc_widgets.js', documentation, callback.chain());
console.log('DONE');
});
if (!this.testmode) work();
////////////////////
function noop(){}
function mkdirPath(path, callback) {
var parts = path.split(/\//);
path = '.';
(function next(){
if (parts.length) {
path += '/' + parts.shift();
fs.mkdir(path, 0777, next);
} else {
callback();
}
})();
}
function copy(name, callback){
fs.readFile(SRC_DIR + name, callback.waitFor(function(err, content){
if (err) return this.error(err);
fs.writeFile(OUTPUT_DIR + name, content, callback);
}));
}
function mergeTemplate(template, output, doc, callback){
fs.readFile(SRC_DIR + template,
callback.waitFor(function(err, template){
if (err) return this.error(err);
var content = mustache.to_html(template.toString(), doc);
fs.writeFile(OUTPUT_DIR + output, content, 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);
throw new Error(error);
}
function valueTag(doc, name, value) {
doc[name] = value;
}
function escapedHtmlTag(doc, name, value) {
doc[name] = value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function markdownTag(doc, name, value) {
doc[name] = markdown(value.replace(/^#/gm, '##')).
replace(/\<pre\>/gmi, '<pre class="brush: xml; brush: js;" ng:non-bindable>');
}
function markdown(text) {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
return new Showdown.converter().makeHtml(text);
}
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');
}
var TAG = {
ngdoc: valueTag,
example: escapedHtmlTag,
scenario: valueTag,
namespace: valueTag,
css: valueTag,
see: valueTag,
usageContent: valueTag,
'function': valueTag,
description: markdownTag,
TODO: markdownTag,
returns: markdownTag,
paramDescription: markdownTag,
exampleDescription: markdownTag,
element: valueTag,
name: function(doc, name, value) {
doc.name = value;
var match = value.match(/^angular[\.\#](([^\.]+)\.(.*)|(.*))/);
doc.shortName = match[3] || match[4];
},
param: function(doc, name, value){
doc.param = doc.param || [];
doc.paramRest = doc.paramRest || [];
var match = value.match(/^({([^\s=]+)(=)?}\s*)?(([^\s=]+)|\[(\S+)+=([^\]]+)\])\s+(.*)/);
if (match) {
var param = {
type: match[2],
name: match[6] || match[5],
'default':match[7],
description:markdownNoP(value.replace(match[0], match[8]))
};
doc.param.push(param);
if (!doc.paramFirst) {
doc.paramFirst = param;
} else {
doc.paramRest.push(param);
}
} else {
throw "[" + doc.raw.file + ":" + doc.raw.line +
"]: @param must be in format '{type} name=value description' got: " + value;
}
}
};
function parseNgDoc(doc){
var atName;
var atText;
var match;
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, trim(atText.join('\n')));
}
atName = match[1];
atText = [];
if(match[3]) atText.push(match[3]);
} else {
if (atName) {
atText.push(line);
} else {
// ignore
}
}
});
if (atName) {
(TAG[atName] || unknownTag)(doc, atName, atText.join('\n'));
}
}
function findNgDoc(file, callback) {
fs.readFile(file, callback.waitFor(function(err, content){
var lines = content.toString().split(NEW_LINE);
var doc;
var match;
var inDoc = false;
lines.forEach(function(line, lineNumber){
lineNumber++;
// is the comment starting?
if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) {
line = match[1];
inDoc = true;
doc = {raw:{file:file, line:lineNumber, text:[]}};
}
// are we done?
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/)){
callback(doc);
}
doc = null;
inDoc = false;
}
// is the comment add text
if (inDoc){
doc.raw.text.push(line.replace(/^\s*\*\s?/, ''));
}
});
callback.done();
}));
}
function findJsFiles(dir, callback){
fs.readdir(dir, callback.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
var path = dir + '/' + file;
fs.lstat(path, callback.waitFor(function(err, stat){
if (err) return this.error(err);
if (stat.isDirectory())
findJsFiles(path, callback.waitMany(callback));
else if (/\.js$/.test(path))
callback(path);
}));
});
callback.done();
}));
}
-37
View File
@@ -1,37 +0,0 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
<pre>
&lt;{{element}} {{shortName}}="{{paramFirst.name}}"&gt;
...
&lt;/{{element}}&gt;
</pre>
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#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
View File
@@ -1 +0,0 @@
NG_DOC={{{JSON}}};
-9
View File
@@ -1,9 +0,0 @@
{{#all}}
describe('{{name}}', function(){
beforeEach(function(){
browser().navigateTo('index.html#{{name}}');
});
// {{raw.file}}:{{raw.line}}
{{{scenario}}}
});
{{/all}}
-7
View File
@@ -1,7 +0,0 @@
function DocController($resource, $location){
this.docs = $resource('documentation.json').get();
this.getPartialDoc = function(){
return encodeURIComponent($location.hashPath) + '.html';
};
}
DocController.$inject=['$resource', '$location'];
-42
View File
@@ -1,42 +0,0 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
<span>{{</span>
{{paramFirst.name}}_expression
| {{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}={{default}}]</i>{{/default}}{{/paramRest}}
<span> }}</span>
</tt>
<h3>In JavaScript</h3>
<tt ng:non-bindable>
angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} );
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
{{/param}}
</ul>
<h3>Returns</h3>
{{{returns}}}
<h3>CSS</h3>
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
-33
View File
@@ -1,33 +0,0 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
&lt;input type="text" ng:format="{{shortName}}"&gt;
</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>
<h3>Returns</h3>
{{{returns}}}
<h3>CSS</h3>
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
-149
View File
@@ -1,149 +0,0 @@
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org/" xmlns:doc="http://docs.angularjs.org/" ng:controller="DocsController">
<head>
<title>&lt;Angular/&gt; Docs</title>
<link rel="stylesheet" href="wiki_widgets.css" type="text/css" media="screen">
<link rel="stylesheet" href="doc_widgets.css" type="text/css" media="screen" />
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css" media="screen" />
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css" media="screen" />
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript" src="../angular.min.js" ng:autobind></script>
<script type="text/javascript" src="doc_widgets.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js"></script>
<script type="text/javascript" src="docs-data.js"></script>
<script type="text/javascript">
SyntaxHighlighter['defaults'].toolbar = false;
DocsController.$inject = ['$location', '$browser']
function DocsController($location, $browser) {
this.docs = NG_DOC;
window.$root = this.$root;
this.getUrl = function(page){
return '#' + encodeURIComponent(page.name);
};
this.getCurrentPartial = function(){
if ($location.hashPath.match(/^angular\./)) {
this.partialUrl = './' + $location.hashPath + '.html';
}
return this.partialUrl;
}
this.getTitle = function(){
if ($location.hashPath.match(/^angular\./)) {
this.partialTitle = $location.hashPath;
}
return this.partialTitle;
}
}
</script>
<style type="text/css" media="screen">
body {
font-family: Arial, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
}
#sidebar {
width: 15em;
float: left;
}
#header {
background-color: #F2C200;
margin-bottom: 1em;
}
#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;
}
#section {
position: absolute;
z-index: -1;
width: 100%;
}
#section ng\:include {
margin: 0 1em 1em 15em;
display: block;
}
#section h1 {
font-family: monospace;
margin-top: 0;
}
#sidebar h2 {
font-size: 1.2em;
margin: 5px 5px 5px 0.8em;
}
#sidebar ul {
list-style-type: none;
/*TODO(esprehn): Can we just reset globally and not break examples?*/
margin: 0;
padding: 0;
}
#sidebar ul li {
margin: 0;
padding: 1px 1px 1px 1.5em;
}
.nav-section {
margin-left: 1em;
margin-top: 0.5em;
}
.section-title {
float: right;
}
</style>
<title>&lt;angular/&gt;: {{getTitle()}}</title>
</head>
<body>
<div id="header">
<h1>
<span class="section-title">{{getTitle()}}</span>
<a href="index.html"><span class="angular">&lt;angular/&gt;</span> Docs</a>
</h1>
</div>
<div id="sidebar" class="nav">
<div ng:repeat="(name, type) in docs.section" class="nav-section">
<h2>{{name}}</h2>
<ul>
<li ng:repeat="page in type.$orderBy('shortName')">
<a href="{{getUrl(page)}}" ng:click="">{{page.shortName}}</a>
</li>
</ul>
</div>
</div>
<div id="section">
<ng:include src="getCurrentPartial()"></ng:include>
</div>
</body>
</html>
-15
View File
@@ -1,15 +0,0 @@
<h1>{{name}}</h1>
{{{description}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+57
View File
@@ -0,0 +1,57 @@
<h1><tt>{{name}}</tt></h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
<h2>Dependencies</h2>
<ul>
{{#requires}}
<li><tt>{{name}}</tt></li>
{{/requires}}
</ul>
{{#method.length}}
<h2>Methods</h2>
<ul>
{{#method}}
<li><tt>{{shortName}}()</tt>: {{{description}}}</li>
{{/method}}
</ul>
{{/method.length}}
{{#property.length}}
<h2>Properties</h2>
<ul>
{{#property}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}</tt>{{#description}}: {{{description}}}{{/description}}</li>
{{/property}}
</ul>
{{/property.length}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
{{#scenario}}<doc:scenario>{{{scenario}}}</doc:scenario>{{/scenario}}
</doc:example>
{{/example}}
-83
View File
@@ -1,83 +0,0 @@
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>&lt;angular/&gt;</tt></p>');
});
});
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',
'default' : undefined,
description : 'Number \n to format.' }]);
});
it('should parse with default', function(){
TAG.param(doc, 'param',
'{(number|string)=} [fractionSize=2] desc');
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'fractionSize',
'default' : '2',
description : 'desc' }]);
});
});
describe('@describe', function(){
it('should support pre blocks', function(){
TAG.description(doc, 'description', '<pre class="brush: xml;" ng:non-bindable>abc</pre>');
expect(doc.description).toEqual('<pre class="brush: xml;" ng:non-bindable>abc</pre>');
});
describe('@example', function(){
it('should not remove {{}}', function(){
TAG.example(doc, 'example', 'text {{ abc }}');
expect(doc.example).toEqual('text {{ abc }}');
});
});
});
});
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');
});
});
});
function load(path){
var sandbox = {
require: require,
console: console,
__dirname: __dirname,
testmode: true
};
Script.runInNewContext(fs.readFileSync(path), sandbox, path);
return sandbox;
}
+257
View File
@@ -0,0 +1,257 @@
var ngdoc = require('ngdoc.js');
describe('ngdoc', function(){
var Doc = ngdoc.Doc;
describe('Doc', function(){
describe('metadata', function(){
it('should find keywords', function(){
expect(new Doc('\nHello: World! @ignore.').keywords()).toEqual('hello world');
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
});
});
describe('parse', function(){
it('should convert @names into properties', function(){
var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n');
doc.parse();
expect(doc.name).toEqual('name');
expect(doc.desc).toEqual('desc\ndesc2');
expect(doc.dep).toEqual('');
});
it('should parse parameters', function(){
var doc = new Doc(
'@param {*} a short\n' +
'@param {Type} b med\n' +
'@param {Class=} [c=2] long\nline');
doc.parse();
expect(doc.param).toEqual([
{name:'a', description:'short', type:'*', optional:false, 'default':undefined},
{name:'b', description:'med', type:'Type', optional:false, 'default':undefined},
{name:'c', description:'long\nline', type:'Class', optional:true, 'default':'2'}
]);
});
it('should parse return', function(){
var doc = new Doc('@returns {Type} text *bold*.');
doc.parse();
expect(doc.returns).toEqual({
type: 'Type',
description: 'text <em>bold</em>.'
});
});
});
});
describe('markdown', function(){
var markdown = ngdoc.markdown;
it('should replace angular in markdown', function(){
expect(markdown('<angular/>')).
toEqual('<p><tt>&lt;angular/&gt;</tt></p>');
});
it('should not replace anything in <pre>', function(){
expect(markdown('bah x\n<pre>\nangular.k\n</pre>\n asdf x')).
toEqual(
'<p>bah x</p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
'angular.k\n' +
'</pre></div>' +
'<p>asdf x</p>');
});
it('should replace text between two <pre></pre> tags', function() {
expect(markdown('<pre>x</pre># One<pre>b</pre>')).
toMatch('</div><h3>One</h3><div');
});
});
describe('trim', function(){
var trim = ngdoc.trim;
it('should remove leading/trailing space', function(){
expect(trim(' \nabc\n ')).toEqual('abc');
});
it('should remove leading space on every line', function(){
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
});
});
describe('merge', function(){
it('should merge child with parent', function(){
var parent = new Doc({name:'angular.service.abc'});
var methodA = new Doc({name:'methodA', methodOf:'angular.service.abc'});
var methodB = new Doc({name:'methodB', methodOf:'angular.service.abc'});
var propA = new Doc({name:'propA', propertyOf:'angular.service.abc'});
var propB = new Doc({name:'propB', propertyOf:'angular.service.abc'});
;var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order;
ngdoc.merge(docs);
expect(docs.length).toEqual(1);
expect(docs[0].name).toEqual('angular.service.abc');
expect(docs[0].methods).toEqual([methodA, methodB]);
expect(docs[0].properties).toEqual([propA, propB]);
});
});
////////////////////////////////////////
describe('TAG', function(){
describe('@param', function(){
it('should parse with no default', function(){
var doc = new Doc('@param {(number|string)} number Number \n to format.');
doc.parse();
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'number',
optional: false,
'default' : undefined,
description : 'Number \n to format.' }]);
});
it('should parse with default and optional', function(){
var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc');
doc.parse();
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'fractionSize',
optional: true,
'default' : '2',
description : 'desc' }]);
});
});
describe('@requires', function() {
it('should parse more @requires tag into array', function() {
var doc = new Doc('@requires $service\n@requires $another');
doc.parse();
expect(doc.requires).toEqual(['$service', '$another']);
});
});
describe('@property', function() {
it('should parse @property tags into array', function() {
var doc = new Doc("@property {type} name1 desc\n@property {type} name2 desc");
doc.parse();
expect(doc.properties.length).toEqual(2);
});
it('should parse @property with only name', function() {
var doc = new Doc("@property fake");
doc.parse();
expect(doc.properties[0].name).toEqual('fake');
});
it('should parse @property with optional type', function() {
var doc = new Doc("@property {string} name");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].type).toEqual('string');
});
it('should parse @property with optional description', function() {
var doc = new Doc("@property name desc rip tion");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].description).toEqual('desc rip tion');
});
it('should parse @property with type and description both', function() {
var doc = new Doc("@property {bool} name desc rip tion");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].type).toEqual('bool');
expect(doc.properties[0].description).toEqual('desc rip tion');
});
});
describe('@returns', function() {
it('should not parse @returns without type', function() {
var doc = new Doc("@returns lala");
expect(doc.parse).toThrow();
});
it('should parse @returns with type and description', function() {
var doc = new Doc("@returns {string} descrip tion");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'});
});
it('should transform description of @returns with markdown', function() {
var doc = new Doc("@returns {string} descrip *tion*");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: 'descrip <em>tion</em>'});
});
it('should support multiline content', function() {
var doc = new Doc("@returns {string} description\n new line\n another line");
doc.parse();
expect(doc.returns).
toEqual({type: 'string', description: 'description\n new line\n another line'});
});
});
describe('@description', function(){
it('should support pre blocks', function(){
var doc = new Doc("@description <pre>abc</pre>");
doc.parse();
expect(doc.description).
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>');
});
it('should support multiple pre blocks', function() {
var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
doc.parse();
expect(doc.description).
toBe('<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
'<h3>bah</h3>\n\n' +
'<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
});
it('should support nested @link annotations with or without description', function() {
var doc = new Doc("@description " +
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
'dad{@link angular.foo}\n\n' +
'{@link angular.directive.ng:foo ng:foo}');
doc.parse();
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 two', function() {
var doc = new Doc('@description # foo\nabc\n## bar \n xyz');
doc.parse();
expect(doc.description).
toBe('<h3>foo</h3>\n\n<p>abc</p>\n\n<h4>bar</h4>\n\n<p>xyz</p>');
});
});
describe('@example', function(){
it('should not remove {{}}', function(){
var doc = new Doc('@example text {{ abc }}');
doc.parse();
expect(doc.example).toEqual('text {{ abc }}');
});
});
describe('@deprecated', function() {
it('should parse @deprecated', function() {
var doc = new Doc('@deprecated Replaced with foo.');
doc.parse();
expect(doc.deprecated).toBe('Replaced with foo.');
});
});
});
});
+39
View File
@@ -0,0 +1,39 @@
if (global.jasmine) return;
require.paths.push(__dirname + "/../../lib");
require.paths.push(__dirname + '/../src');
var jasmine = require('jasmine-1.0.1');
var sys = require('util');
for(var key in jasmine) {
global[key] = jasmine[key];
}
//Patch Jasmine for proper stack traces
jasmine.Spec.prototype.fail = function (e) {
var expectationResult = new jasmine.ExpectationResult({
passed: false,
message: e ? jasmine.util.formatException(e) : 'Exception'
});
// PATCH
if (e) {
expectationResult.trace = e;
}
this.results_.addResult(expectationResult);
};
var isVerbose = false;
var showColors = true;
process.argv.forEach(function(arg){
switch(arg) {
case '--color': showColors = true; break;
case '--noColor': showColors = false; break;
case '--verbose': isVerbose = true; break;
}
});
jasmine.executeSpecsInFolder(__dirname, function(runner, log){
process.exit(runner.results().failedCount);
}, isVerbose, showColors);
+18
View File
@@ -0,0 +1,18 @@
var writer = require('writer.js');
describe('writer', function(){
describe('toString', function(){
var toString = writer.toString;
it('should merge string', function(){
expect(toString('abc')).toEqual('abc');
});
it('should merge obj', function(){
expect(toString({a:1})).toEqual('{"a":1}');
});
it('should merge array', function(){
expect(toString(['abc',{}])).toEqual('abc{}');
});
});
});
-21
View File
@@ -1,21 +0,0 @@
require.paths.push("./lib");
var jasmine = require('jasmine-1.0.1');
var sys = require('sys');
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);
+4 -1
View File
@@ -2,7 +2,10 @@ function noop(){}
function chain(delegateFn, explicitDone){
var onDoneFn = noop;
var onErrorFn = noop;
var onErrorFn = function(e){
console.error(e.stack || e);
process.exit(-1);
};
var waitForCount = 1;
delegateFn = delegateFn || noop;
var stackError = new Error('capture stack');
+123
View File
@@ -0,0 +1,123 @@
/**
* DOM generation class
*/
exports.DOM = DOM;
//////////////////////////////////////////////////////////
function DOM(){
this.out = [];
this.headingDepth = 1;
}
var INLINE_TAGS = {
i: true,
b: true
};
DOM.prototype = {
toString: function() {
return this.out.join('');
},
text: function(content) {
if (typeof content == "string") {
this.out.push(content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'));
} else if (typeof content == 'function') {
content.call(this, this);
} else if (content instanceof Array) {
this.ul(content);
}
},
html: function(html) {
if (html) {
this.out.push(html);
}
},
tag: function(name, attr, text) {
if (!text) {
text = attr;
attr = {};
if (name == 'code')
attr['ng:non-bindable'] = '';
}
this.out.push('<' + name);
for(var key in attr) {
this.out.push(" " + key + '="' + attr[key] + '"');
}
this.out.push('>');
this.text(text);
this.out.push('</' + name + '>');
if (!INLINE_TAGS[name])
this.out.push('\n');
},
code: function(text) {
this.tag('div', {'ng:non-bindable':''}, function(){
this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
});
},
example: function(source, scenario) {
if (source || scenario) {
this.h('Example', function(){
if (scenario === false) {
this.code(source);
} else {
this.tag('doc:example', function(){
if (source) this.tag('doc:source', source);
if (scenario) this.tag('doc:scenario', scenario);
});
}
});
}
},
h: function(heading, content, fn){
if (content==undefined || content && content.legth == 0) return;
this.tag('h' + this.headingDepth, heading);
this.headingDepth++;
if (content instanceof Array) {
this.ul(content, {'class': heading.toLowerCase()}, fn);
} else if (fn) {
fn.call(this, content);
} else {
this.text(content);
}
this.headingDepth--;
},
h1: function(attr, text) {
this.tag('h1', attr, text);
},
h2: function(attr, text) {
this.tag('h2', attr, text);
},
h3: function(attr, text) {
this.tag('h3', attr, text);
},
p: function(attr, text) {
this.tag('p', attr, text);
},
ul: function(list, attr, fn) {
if (typeof attr == 'function') {
fn = attr;
attr = {};
}
this.tag('ul', attr, function(dom){
list.forEach(function(item){
dom.out.push('<li>');
dom.text(fn ? fn(item) : item);
dom.out.push('</li>\n');
});
});
}
};
+48
View File
@@ -0,0 +1,48 @@
require.paths.push(__dirname);
require.paths.push('lib');
var reader = require('reader.js'),
ngdoc = require('ngdoc.js'),
writer = require('writer.js'),
callback = require('callback.js');
var docs = [];
var start;
var work = callback.chain(function(){
start = now();
console.log('Generating Angular Reference Documentation...');
reader.collect(work.waitMany(function(text, file, line){
var doc = new ngdoc.Doc(text, file, line);
docs.push(doc);
doc.parse();
}));
});
var writes = callback.chain(function(){
ngdoc.merge(docs);
docs.forEach(function(doc){
writer.output(doc.name + '.html', doc.html(), writes.waitFor());
});
var metadata = ngdoc.metadata(docs);
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata), ';'], writes.waitFor());
writer.copy('index.html', writes.waitFor());
writer.copy('docs.js', writes.waitFor());
writer.copy('docs.css', writes.waitFor());
writer.copy('doc_widgets.js', writes.waitFor());
writer.copy('doc_widgets.css', writes.waitFor());
writer.copy('docs-scenario.html', writes.waitFor());
writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
writer.copy('syntaxhighlighter/shBrushJScript.js', writes.waitFor());
writer.copy('syntaxhighlighter/shBrushXml.js', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.css', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.js', writes.waitFor());
writer.copy('syntaxhighlighter/shThemeDefault.css', writes.waitFor());
writer.copy('jquery.min.js', writes.waitFor());
});
writes.onDone(function(){
console.log('DONE. Generated ' + docs.length + ' pages in ' +
(now()-start) + 'ms.' );
});
work.onDone(writes);
writer.makeDir('build/docs/syntaxhighlighter', work);
///////////////////////////////////
function now(){ return new Date().getTime(); }
View File
+614
View File
@@ -0,0 +1,614 @@
/**
* All parsing/transformation code goes here. All code here should be sync to ease testing.
*/
var Showdown = require('showdown').Showdown;
var DOM = require('dom.js').DOM;
var NEW_LINE = /\n\r?/;
exports.markdown = markdown;
exports.markdownNoP = markdownNoP;
exports.trim = trim;
exports.metadata = metadata;
exports.scenarios = scenarios;
exports.merge = merge;
exports.Doc = Doc;
//////////////////////////////////////////////////////////
function Doc(text, file, line) {
if (typeof text == 'object') {
for ( var key in text) {
this[key] = text[key];
}
} else {
this.text = text;
this.file = file;
this.line = line;
}
}
Doc.METADATA_IGNORE = (function(){
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
return words.toString().split(/[,\s\n\r]+/gm);
})();
Doc.prototype = {
keywords: function keywords(){
var keywords = {};
Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
var words = [];
var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
tokens.forEach(function(key){
var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/);
if (match){
key = match[1];
if (!keywords[key]) {
keywords[key] = true;
words.push(key);
}
}
});
words.sort();
return words.join(' ');
},
parse: function(){
var atName;
var atText;
var match;
var self = this;
self.text.split(NEW_LINE).forEach(function(line){
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
// we found @name ...
// if we have existing name
flush();
atName = match[1];
atText = [];
if(match[3]) atText.push(match[3]);
} else {
if (atName) {
atText.push(line);
}
}
});
flush();
this.shortName = (this.name || '').split(/[\.#]/).pop();
this.description = markdown(this.description);
function flush(){
if (atName) {
var text = trim(atText.join('\n'));
if (atName == 'param') {
var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 12 2 34 4 5 5 6 6 3 7 7
if (!match) {
throw new Error("Not a valid 'param' format: " + text);
}
var param = {
name: match[5] || match[4],
description:markdownNoP(text.replace(match[0], match[7])),
type: match[1],
optional: !!match[2],
'default':match[6]
};
self.param = self.param || [];
self.param.push(param);
} else if (atName == 'returns') {
var match = text.match(/^{([^}=]+)}\s+(.*)/);
if (!match) {
throw new Error("Not a valid 'returns' format: " + text);
}
self.returns = {
type: match[1],
description: markdownNoP(text.replace(match[0], match[2]))
};
} else if(atName == 'requires') {
self.requires = self.requires || [];
self.requires.push(text);
} else if(atName == 'property') {
var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
if (!match) {
throw new Error("Not a valid 'property' format: " + text);
}
var property = {
type: match[2],
name: match[3],
description: match[5] || ''
};
self.properties = self.properties || [];
self.properties.push(property);
} else {
self[atName] = text;
}
}
}
},
html: function(){
var dom = new DOM(),
self = this;
dom.h(this.name, function(){
notice('workInProgress', 'Work in Progress',
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
notice('depricated', 'Depricated API');
dom.h('Description', self.description, html);
dom.h('Dependencies', self.requires);
usage();
dom.h('Methods', self.methods, function(method){
var signature = (method.param || []).map(property('name'));
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description);
method.html_usage_parameters(dom);
dom.example(method.example, false);
});
});
dom.h('Properties', self.properties, function(property){
dom.h(property.name, function(){
dom.text(property.description);
dom.example(property.example, false);
});
});
dom.example(self.example, self.scenario);
});
return dom.toString();
//////////////////////////
function html(text){
this.html(text);
}
function usage(){
(self['html_usage_' + self.ngdoc] || function(){
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom);
}
function section(name, property, fn) {
var value = self[property];
if (value) {
dom.h2(name);
if (typeof value == 'string') {
value = markdown(value) + '\n';
fn ? fn(value) : dom.html(value);
} else if (value instanceof Array) {
dom.ul(value, fn);
}
}
}
function notice(name, legend, msg){
if (self[name] == undefined) return;
dom.tag('fieldset', {'class':name}, function(dom){
dom.tag('legend', legend);
dom.text(msg);
});
}
},
html_usage_parameters: function(dom) {
dom.h('Parameters', this.param, function(param){
dom.tag('code', function(){
dom.text(param.name);
if (param.optional) {
dom.tag('i', function(){
dom.text('(optional');
if(param['default']) {
dom.text('=' + param['default']);
}
dom.text(')');
});
}
dom.text(' {');
dom.text(param.type);
dom.text('} ');
});
dom.html(param.description);
});
},
html_usage_returns: function(dom) {
var self = this;
if (self.returns) {
dom.h('Returns', function(){
dom.tag('code', self.returns.type);
dom.text(' ');
dom.html(self.returns.description);
});
}
},
html_usage_function: function(dom){
var self = this;
dom.h('Usage', function(){
dom.code(function(){
dom.text(self.name);
dom.text('(');
var first = true;
(self.param || []).forEach(function(param){
if (first) {
first = false;
} else {
dom.text(', ');
}
dom.text(param.name);
});
dom.text(');');
});
self.html_usage_parameters(dom);
self.html_usage_returns(dom);
});
},
html_usage_directive: function(dom){
var self = this;
dom.h('Usage', function(){
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
dom.text('<' + self.element + ' ');
dom.text(self.shortName);
if (self.param) {
dom.text('="' + self.param[0].name + '"');
}
dom.text('>\n ...\n');
dom.text('</' + self.element + '>');
});
self.html_usage_parameters(dom);
});
},
html_usage_filter: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.tag('code', function(){
dom.text('{{ ');
dom.text(self.shortName);
dom.text('_expression | ');
dom.text(self.shortName);
var first = true;
(self.param||[]).forEach(function(param){
if (first) {
first = false;
} else {
if (param.optional) {
dom.tag('i', function(){
dom.text('[:' + param.name + ']');
});
} else {
dom.text(':' + param.name);
}
}
});
dom.text(' }}');
});
});
dom.h3('In JavaScript', function(){
dom.tag('code', function(){
dom.text('angular.filter.');
dom.text(self.shortName);
dom.text('(');
var first = true;
(self.param||[]).forEach(function(param){
if (first) {
first = false;
dom.text(param.name);
} else {
if (param.optional) {
dom.tag('i', function(){
dom.text('[, ' + param.name + ']');
});
} else {
dom.text(', ' + param.name);
}
}
});
dom.text(')');
});
});
self.html_usage_parameters(dom);
self.html_usage_returns(dom);
});
},
html_usage_formatter: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
dom.text('<input type="text" ng:format="');
dom.text(self.shortName);
dom.text('">');
});
});
dom.h3('In JavaScript', function(){
dom.code(function(){
dom.text('var userInputString = angular.formatter.');
dom.text(self.shortName);
dom.text('.format(modelValue);');
});
dom.html('<br/>');
dom.code(function(){
dom.text('var modelValue = angular.formatter.');
dom.text(self.shortName);
dom.text('.parse(userInputString);');
});
});
self.html_usage_returns(dom);
});
},
html_usage_validator: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
dom.text('<input type="text" ng:validate="');
dom.text(self.shortName);
var first = true;
(self.param||[]).forEach(function(param){
if (first) {
first = false;
} else {
if (param.optional) {
dom.text('[:' + param.name + ']');
} else {
dom.text(':' + param.name);
}
}
});
dom.text('"/>');
});
});
dom.h('In JavaScript', function(){
dom.code(function(){
dom.text('angular.validator.');
dom.text(self.shortName);
dom.text('(');
var first = true;
(self.param||[]).forEach(function(param){
if (first) {
first = false;
dom.text(param.name);
} else {
if (param.optional) {
dom.text('[, ' + param.name + ']');
} else {
dom.text(', ' + param.name);
}
}
});
dom.text(')');
});
});
self.html_usage_parameters(dom);
self.html_usage_returns(dom);
});
},
html_usage_widget: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
if (self.shortName.match(/^@/)) {
dom.text('<');
dom.text(self.element);
dom.text(' ');
dom.text(self.shortName.substring(1));
if (self.param) {
dom.text('="');
dom.text(self.param[0].name);
dom.text('"');
}
dom.text('>\n ...\n</');
dom.text(self.element);
dom.text('>');
} else {
dom.text('<');
dom.text(self.shortName);
(self.param||[]).forEach(function(param){
if (param.optional) {
dom.text(' [' + param.name + '="..."]');
} else {
dom.text(' ' + param.name + '="..."');
}
});
dom.text('></');
dom.text(self.shortName);
dom.text('>');
}
});
});
self.html_usage_parameters(dom);
self.html_usage_returns(dom);
});
},
html_usage_overview: function(dom){
},
html_usage_service: function(dom){
}
};
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
function markdown (text) {
if (!text) return text;
var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/),
match;
parts.forEach(function(text, i){
if (text.match(/^<pre>/)) {
text = text.
replace(/^<pre>/, '<div ng:non-bindable><pre class="brush: js; html-script: true;">').
replace(/<\/pre>/, '</pre></div>');
} else {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###'));
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('');
};
var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m;
// 1 123 3 4 42
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 scenarios(docs){
var specs = [];
docs.forEach(function(doc){
if (doc.scenario) {
specs.push('describe("');
specs.push(doc.name);
specs.push('", function(){\n');
specs.push(' beforeEach(function(){\n');
specs.push(' browser().navigateTo("index.html#!' + doc.name + '");');
specs.push(' });\n\n');
specs.push(doc.scenario);
specs.push('\n});\n\n');
}
});
return specs;
}
//////////////////////////////////////////////////////////
function metadata(docs){
var words = [];
docs.forEach(function(doc){
words.push({
name:doc.name,
type: doc.ngdoc,
keywords:doc.keywords()
});
});
words.sort(keywordSort);
return words;
}
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;
}
//////////////////////////////////////////////////////////
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 merge(docs){
var byName = {};
docs.forEach(function(doc){
byName[doc.name] = doc;
});
for(var i=0; i<docs.length;) {
if (findParent(docs[i], 'method') ||
findParent(docs[i], 'property')) {
docs.splice(i, 1);
} else {
i++;
}
}
function findParent(doc, name){
var parentName = doc[name+'Of'];
if (!parentName) return false;
var parent = byName[parentName];
if (!parent)
throw new Error("No parent named '" + parentName + "' for '" +
doc.name + "' in @" + name + "Of.");
var listName = (name + 's').replace(/ys$/, 'ies');
var list = parent[listName] = (parent[listName] || []);
list.push(doc);
list.sort(orderByName);
return true;
}
function orderByName(a, b){
return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0);
}
}
//////////////////////////////////////////////////////////
function property(name) {
return function(value){
return value[name];
};
}
+91
View File
@@ -0,0 +1,91 @@
/**
* All reading related code here. This is so that we can separate the async code from sync code
* for testability
*/
require.paths.push(__dirname);
var fs = require('fs'),
callback = require('callback');
var NEW_LINE = /\n\r?/;
function collect(callback){
findJsFiles('src', callback.waitMany(function(file) {
//console.log('reading', file, '...');
findNgDocInJsFile(file, callback.waitMany(function(doc, line) {
callback(doc, file, line);
}));
}));
findNgDocInDir('docs/', callback.waitMany(callback));
callback.done();
}
function findJsFiles(dir, callback){
fs.readdir(dir, callback.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
var path = dir + '/' + file;
fs.lstat(path, callback.waitFor(function(err, stat){
if (err) return this.error(err);
if (stat.isDirectory())
findJsFiles(path, callback.waitMany(callback));
else if (/\.js$/.test(path))
callback(path);
}));
});
callback.done();
}));
}
function findNgDocInDir(directory, docNotify) {
fs.readdir(directory, docNotify.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
//console.log('reading', directory + file, '...');
if (!file.match(/\.ngdoc$/)) return;
fs.readFile(directory + file, docNotify.waitFor(function(err, content){
if (err) return this.error(err);
docNotify(content.toString(), directory + file, 1);
}));
});
docNotify.done();
}));
}
function findNgDocInJsFile(file, callback) {
fs.readFile(file, callback.waitFor(function(err, content){
var lines = content.toString().split(NEW_LINE);
var text;
var startingLine ;
var match;
var inDoc = false;
lines.forEach(function(line, lineNumber){
lineNumber++;
// is the comment starting?
if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) {
line = match[1];
inDoc = true;
text = [];
startingLine = lineNumber;
}
// are we done?
if (inDoc && line.match(/\*\//)) {
text = text.join('\n');
text = text.replace(/^\n/, '');
if (text.match(/@ngdoc/)){
callback(text, startingLine);
}
doc = null;
inDoc = false;
}
// is the comment add text
if (inDoc){
text.push(line.replace(/^\s*\*\s?/, ''));
}
});
callback.done();
}));
}
exports.collect = collect;
@@ -54,10 +54,6 @@
} catch (e) {
alert(e);
}
return function() {
SyntaxHighlighter.highlight();
};
});
function indent(text) {
+262
View File
@@ -0,0 +1,262 @@
/* Common Style */
body {
font-family: Arial, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
}
a {
color: blue;
}
/* Main Layout */
#header {
height: 3.5em;
}
#sidebar,
#main {
position: absolute;
top: 3.5em;
bottom: 0;
margin-top: 1px;
overflow-x: hidden;
}
#sidebar {
width: 13.8em;
padding: 0.8em 0em 1.5em 0.8em;
}
#main {
left: 14.6em;
right: 0;
padding: 1em;
overflow-y: scroll;
}
#api-list {
position: absolute;
top: 3em;
bottom: 1em;
overflow-y: scroll;
padding-right: 0.8em;
}
/* App Header */
#header {
background-color: #F2C200;
border-bottom: 1px solid #957800;
}
#header h1 {
font-weight: normal;
font-size: 30px;
line-height: 30px;
margin: 0;
padding: 10px 10px;
height: 30px;
}
#header .angular {
font-family: "Courier New", monospace;
font-weight: bold;
}
#header h1 a {
color: black;
text-decoration: none;
}
#header h1 a:hover {
text-decoration: underline;
}
/* Main main Style */
#main h1 {
font-family: monospace;
margin-top: 0;
padding-bottom: 5px;
border-bottom: 1px solid #CCC;
}
#main h2 {
margin-top: 1.8em;
}
#main h1 + h2 {
margin-top: 1.3em;
}
#main h3 {
margin-top: 1.5em;
}
#main ul.methods h3,
#main ul.properties h3 {
margin-top: 1.5em;
font-family: "Courier New", monospace;
}
.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;
}
+47
View File
@@ -0,0 +1,47 @@
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 ....");
};
}
angular.filter('short', function(name){
return (name||'').split(/\./).pop();
});
SyntaxHighlighter['defaults'].toolbar = false;
+46
View File
@@ -0,0 +1,46 @@
<!doctype html>
<html xmlns:ng="http://angularjs.org/"
xmlns:doc="http://docs.angularjs.org/"
ng:controller="DocsController">
<head>
<title ng:bind-template="&lt;angular/&gt;: {{getTitle()}}">&lt;angular/&gt;</title>
<meta name="fragment" content="!">
<link rel="stylesheet" href="doc_widgets.css" type="text/css" />
<link rel="stylesheet" href="docs.css" type="text/css"/>
<link rel="stylesheet" href="syntaxhighlighter/shCore.css" type="text/css"/>
<link rel="stylesheet" href="syntaxhighlighter/shThemeDefault.css" type="text/css"/>
<script src="jquery.min.js"></script>
<script src="syntaxhighlighter/shCore.js"></script>
<script src="syntaxhighlighter/shBrushJScript.js"></script>
<script src="syntaxhighlighter/shBrushXml.js"></script>
<script src="../angular.min.js" ng:autobind></script>
<script src="docs.js"></script>
<script src="doc_widgets.js"></script>
<script src="docs-keywords.js"></script>
</head>
<body style="display:none;" ng:show="true">
<div id="header">
<h1>
<span class="main-title">{{getTitle()}}</span>
<a href="#" tabindex="0"><span class="angular">&lt;angular/&gt;</span> Docs</a>
</h1>
</div>
<div id="sidebar">
<input type="text" name="search" id="search-box" placeholder="search the docs"
tabindex="1" accesskey="s"/>
<ul id="api-list">
<li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)">
<a href="{{getUrl(page)}}" ng:click="" tabindex="2">{{page.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>
+1
View File
@@ -0,0 +1 @@
../../../lib/jquery/jquery-1.4.2.min.js
+52
View File
@@ -0,0 +1,52 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
;(function()
{
// CommonJS
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
function Brush()
{
var keywords = 'break case catch continue ' +
'default delete do else false ' +
'for function if in instanceof ' +
'new null return super switch ' +
'this throw true try typeof var while with'
;
var r = SyntaxHighlighter.regexLib;
this.regexList = [
{ regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings
{ regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings
{ regex: r.singleLineCComments, css: 'comments' }, // one line comments
{ regex: r.multiLineCComments, css: 'comments' }, // multiline comments
{ regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion
{ regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords
];
this.forHtmlScript(r.scriptScriptTags);
};
Brush.prototype = new SyntaxHighlighter.Highlighter();
Brush.aliases = ['js', 'jscript', 'javascript'];
SyntaxHighlighter.brushes.JScript = Brush;
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();
+69
View File
@@ -0,0 +1,69 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
;(function()
{
// CommonJS
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
function Brush()
{
function process(match, regexInfo)
{
var constructor = SyntaxHighlighter.Match,
code = match[0],
tag = new XRegExp('(&lt;|<)[\\s\\/\\?]*(?<name>[:\\w-\\.]+)', 'xg').exec(code),
result = []
;
if (match.attributes != null)
{
var attributes,
regex = new XRegExp('(?<name> [\\w:\\-\\.]+)' +
'\\s*=\\s*' +
'(?<value> ".*?"|\'.*?\'|\\w+)',
'xg');
while ((attributes = regex.exec(code)) != null)
{
result.push(new constructor(attributes.name, match.index + attributes.index, 'color1'));
result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string'));
}
}
if (tag != null)
result.push(
new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword')
);
return result;
}
this.regexList = [
{ regex: new XRegExp('(\\&lt;|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\&gt;|>)', 'gm'), css: 'color2' }, // <![ ... [ ... ]]>
{ regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // <!-- ... -->
{ regex: new XRegExp('(&lt;|<)[\\s\\/\\?]*(\\w+)(?<attributes>.*?)[\\s\\/\\?]*(&gt;|>)', 'sg'), func: process }
];
};
Brush.prototype = new SyntaxHighlighter.Highlighter();
Brush.aliases = ['xml', 'xhtml', 'xslt', 'html'];
SyntaxHighlighter.brushes.Xml = Brush;
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();
@@ -0,0 +1,226 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
.syntaxhighlighter a,
.syntaxhighlighter div,
.syntaxhighlighter code,
.syntaxhighlighter table,
.syntaxhighlighter table td,
.syntaxhighlighter table tr,
.syntaxhighlighter table tbody,
.syntaxhighlighter table thead,
.syntaxhighlighter table caption,
.syntaxhighlighter textarea {
-moz-border-radius: 0 0 0 0 !important;
-webkit-border-radius: 0 0 0 0 !important;
background: none !important;
border: 0 !important;
bottom: auto !important;
float: none !important;
height: auto !important;
left: auto !important;
line-height: 1.1em !important;
margin: 0 !important;
outline: 0 !important;
overflow: visible !important;
padding: 0 !important;
position: static !important;
right: auto !important;
text-align: left !important;
top: auto !important;
vertical-align: baseline !important;
width: auto !important;
box-sizing: content-box !important;
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important;
font-weight: normal !important;
font-style: normal !important;
font-size: 1em !important;
min-height: inherit !important;
min-height: auto !important;
}
.syntaxhighlighter {
width: 100% !important;
margin: 1em 0 1em 0 !important;
position: relative !important;
overflow: auto !important;
font-size: 1em !important;
}
.syntaxhighlighter.source {
overflow: hidden !important;
}
.syntaxhighlighter .bold {
font-weight: bold !important;
}
.syntaxhighlighter .italic {
font-style: italic !important;
}
.syntaxhighlighter .line {
white-space: pre !important;
}
.syntaxhighlighter table {
width: 100% !important;
}
.syntaxhighlighter table caption {
text-align: left !important;
padding: .5em 0 0.5em 1em !important;
}
.syntaxhighlighter table td.code {
width: 100% !important;
}
.syntaxhighlighter table td.code .container {
position: relative !important;
}
.syntaxhighlighter table td.code .container textarea {
box-sizing: border-box !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
height: 100% !important;
border: none !important;
background: white !important;
padding-left: 1em !important;
overflow: hidden !important;
white-space: pre !important;
}
.syntaxhighlighter table td.gutter .line {
text-align: right !important;
padding: 0 0.5em 0 1em !important;
}
.syntaxhighlighter table td.code .line {
padding: 0 1em !important;
}
.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
padding-left: 0em !important;
}
.syntaxhighlighter.show {
display: block !important;
}
.syntaxhighlighter.collapsed table {
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar {
padding: 0.1em 0.8em 0em 0.8em !important;
font-size: 1em !important;
position: static !important;
width: auto !important;
height: auto !important;
}
.syntaxhighlighter.collapsed .toolbar span {
display: inline !important;
margin-right: 1em !important;
}
.syntaxhighlighter.collapsed .toolbar span a {
padding: 0 !important;
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar span a.expandSource {
display: inline !important;
}
.syntaxhighlighter .toolbar {
position: absolute !important;
right: 1px !important;
top: 1px !important;
width: 11px !important;
height: 11px !important;
font-size: 10px !important;
z-index: 10 !important;
}
.syntaxhighlighter .toolbar span.title {
display: inline !important;
}
.syntaxhighlighter .toolbar a {
display: block !important;
text-align: center !important;
text-decoration: none !important;
padding-top: 1px !important;
}
.syntaxhighlighter .toolbar a.expandSource {
display: none !important;
}
.syntaxhighlighter.ie {
font-size: .9em !important;
padding: 1px 0 1px 0 !important;
}
.syntaxhighlighter.ie .toolbar {
line-height: 8px !important;
}
.syntaxhighlighter.ie .toolbar a {
padding-top: 0px !important;
}
.syntaxhighlighter.printing .line.alt1 .content,
.syntaxhighlighter.printing .line.alt2 .content,
.syntaxhighlighter.printing .line.highlighted .number,
.syntaxhighlighter.printing .line.highlighted.alt1 .content,
.syntaxhighlighter.printing .line.highlighted.alt2 .content {
background: none !important;
}
.syntaxhighlighter.printing .line .number {
color: #bbbbbb !important;
}
.syntaxhighlighter.printing .line .content {
color: black !important;
}
.syntaxhighlighter.printing .toolbar {
display: none !important;
}
.syntaxhighlighter.printing a {
text-decoration: none !important;
}
.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
color: black !important;
}
.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
color: #008200 !important;
}
.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
color: blue !important;
}
.syntaxhighlighter.printing .keyword {
color: #006699 !important;
font-weight: bold !important;
}
.syntaxhighlighter.printing .preprocessor {
color: gray !important;
}
.syntaxhighlighter.printing .variable {
color: #aa7700 !important;
}
.syntaxhighlighter.printing .value {
color: #009900 !important;
}
.syntaxhighlighter.printing .functions {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .constants {
color: #0066cc !important;
}
.syntaxhighlighter.printing .script {
font-weight: bold !important;
}
.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
color: gray !important;
}
.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
color: red !important;
}
.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
color: black !important;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
.syntaxhighlighter {
background-color: white !important;
}
.syntaxhighlighter .line.alt1 {
background-color: white !important;
}
.syntaxhighlighter .line.alt2 {
background-color: white !important;
}
.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
background-color: #e0e0e0 !important;
}
.syntaxhighlighter .line.highlighted.number {
color: black !important;
}
.syntaxhighlighter table caption {
color: black !important;
}
.syntaxhighlighter .gutter {
color: #afafaf !important;
}
.syntaxhighlighter .gutter .line {
border-right: 3px solid #6ce26c !important;
}
.syntaxhighlighter .gutter .line.highlighted {
background-color: #6ce26c !important;
color: white !important;
}
.syntaxhighlighter.printing .line .content {
border: none !important;
}
.syntaxhighlighter.collapsed {
overflow: visible !important;
}
.syntaxhighlighter.collapsed .toolbar {
color: blue !important;
background: white !important;
border: 1px solid #6ce26c !important;
}
.syntaxhighlighter.collapsed .toolbar a {
color: blue !important;
}
.syntaxhighlighter.collapsed .toolbar a:hover {
color: red !important;
}
.syntaxhighlighter .toolbar {
color: white !important;
background: #6ce26c !important;
border: none !important;
}
.syntaxhighlighter .toolbar a {
color: white !important;
}
.syntaxhighlighter .toolbar a:hover {
color: black !important;
}
.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
color: black !important;
}
.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
color: #008200 !important;
}
.syntaxhighlighter .string, .syntaxhighlighter .string a {
color: blue !important;
}
.syntaxhighlighter .keyword {
color: #006699 !important;
}
.syntaxhighlighter .preprocessor {
color: gray !important;
}
.syntaxhighlighter .variable {
color: #aa7700 !important;
}
.syntaxhighlighter .value {
color: #009900 !important;
}
.syntaxhighlighter .functions {
color: #ff1493 !important;
}
.syntaxhighlighter .constants {
color: #0066cc !important;
}
.syntaxhighlighter .script {
font-weight: bold !important;
color: #006699 !important;
background-color: none !important;
}
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
color: gray !important;
}
.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
color: red !important;
}
.syntaxhighlighter .keyword {
font-weight: bold !important;
}
@@ -0,0 +1,2 @@
3.0.83
+61
View File
@@ -0,0 +1,61 @@
/**
* All writing related code here. This is so that we can separate the async code from sync code
* for testability
*/
require.paths.push(__dirname);
var fs = require('fs');
var OUTPUT_DIR = "build/docs/";
function output(docs, content, callback){
callback();
}
exports.output = function(file, content, callback){
//console.log('writing', OUTPUT_DIR + file, '...');
fs.writeFile(
OUTPUT_DIR + file,
exports.toString(content),
callback);
};
exports.toString = function toString(obj){
switch (typeof obj) {
case 'string':
return obj;
case 'object':
if (obj instanceof Array) {
obj.forEach(function (value, key){
obj[key] = toString(value);
});
return obj.join('');
} else {
return JSON.stringify(obj);
}
}
return obj;
};
exports.makeDir = function (path, callback) {
var parts = path.split(/\//);
path = '.';
(function next(){
if (parts.length) {
path += '/' + parts.shift();
fs.mkdir(path, 0777, next);
} else {
callback();
}
})();
};
exports.copy = function(filename, callback){
//console.log('writing', OUTPUT_DIR + filename, '...');
fs.readFile('docs/src/templates/' + filename, function(err, content){
if (err) return callback.error(err);
fs.writeFile(
OUTPUT_DIR + filename,
content,
callback);
});
};
-39
View File
@@ -1,39 +0,0 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
&lt;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}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
<h3>CSS</h3>
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
-39
View File
@@ -1,39 +0,0 @@
<h1>{{name}}</h1>
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
<pre>
&lt;{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}&gt;{{#usageContent}}
{{usageContent}}
{{/usageContent}}&lt;/{{shortName}}&gt;
</pre>
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
<h3>CSS</h3>
{{{css}}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+1 -1
View File
@@ -9,7 +9,7 @@
<!-- TODO: we need to expose $root so that we can delete cookies in the scenario runner, there
must be a better way to do this -->
<body ng:controller="example.personalLog.LogCtrl" ng:init="$window.$root = $root">
<body ng:controller="example.personalLog.LogCtrl">
<form action="" ng:submit="addLog(newMsg)">
<input type="text" name="newMsg" />
@@ -81,15 +81,14 @@ angular.scenario.dsl('clearCookies', function() {
*/
return function() {
this.addFutureAction('clear all cookies', function($window, $document, done) {
//TODO: accessing angular services is pretty nasty, we need a better way to reach them
var $cookies = $window.$root.$cookies,
var rootScope = $window.angular.element($document[0]).data('$scope'),
$cookies = rootScope.$service('$cookies'),
cookieName;
for (cookieName in $cookies) {
console.log('deleting cookie: ' + cookieName);
delete $cookies[cookieName];
}
$window.$root.$eval();
rootScope.$eval();
done();
});
@@ -3,6 +3,7 @@ describe('example.personalLog.LogCtrl', function() {
function createNotesCtrl() {
var scope = angular.scope();
scope.$cookies = scope.$service('$cookies');
return scope.$new(example.personalLog.LogCtrl);
}
+3 -3
View File
@@ -1,3 +1,3 @@
#!/bin/sh
node docs/specs.js --noColor && node docs/collect.js
#!/bin/bash
. ~/.bashrc
node docs/spec/specs.js --noColor && node docs/src/gen-docs.js
+34
View File
@@ -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"
+2
View File
@@ -8,6 +8,7 @@ load:
- src/Angular.js
- src/JSON.js
- src/*.js
- example/personalLog/*.js
- test/testabilityPatch.js
- src/scenario/Scenario.js
- src/scenario/output/*.js
@@ -16,6 +17,7 @@ load:
- test/scenario/*.js
- test/scenario/output/*.js
- test/*.js
- example/personalLog/test/*.js
exclude:
- src/angular.prefix
+13
View File
@@ -0,0 +1,13 @@
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
- build/angular.min.js
- perf/data/*.js
- perf/testUtils.js
- perf/*.js
exclude:
+160 -95
View File
@@ -1,111 +1,176 @@
/**
* @fileoverview Jasmine JsTestDriver Adapter.
* @author ibolmo@gmail.com (Olmo Maldonado)
* @author misko@hevery.com (Misko Hevery)
*/
(function(window) {
var rootDescribes = new Describes(window);
var describePath = [];
rootDescribes.collectMode();
var jasmineTest = TestCase('Jasmine Adapter Tests');
var jasminePlugin = {
name:'jasmine',
runTestConfiguration: function(testRunConfiguration, onTestDone, onTestRunConfigurationComplete){
if (testRunConfiguration.testCaseInfo_.template_ !== jasmineTest) return;
var jasmineEnv = jasmine.currentEnv_ = new jasmine.Env();
rootDescribes.playback();
var specLog = jstestdriver.console.log_ = [];
var start;
jasmineEnv.specFilter = function(spec) {
return rootDescribes.isExclusive(spec);
};
jasmineEnv.reporter = {
log: function(str){
specLog.push(str);
},
(function() {
reportRunnerStarting: function(runner) { },
function bind(_this, _function){
return function(){
return _function.call(_this);
};
reportSpecStarting: function(spec) {
specLog = jstestdriver.console.log_ = [];
start = new Date().getTime();
},
reportSpecResults: function(spec) {
var suite = spec.suite;
var results = spec.results();
if (results.skipped) return;
var end = new Date().getTime();
var messages = [];
var resultItems = results.getItems();
var state = 'passed';
for ( var i = 0; i < resultItems.length; i++) {
if (!resultItems[i].passed()) {
state = resultItems[i].message.match(/AssertionError:/) ? 'error' : 'failed';
messages.push(resultItems[i].toString());
messages.push(formatStack(resultItems[i].trace.stack));
}
}
onTestDone(
new jstestdriver.TestResult(
suite.getFullName(),
spec.description,
state,
messages.join('\n'),
specLog.join('\n'),
end - start));
},
reportSuiteResults: function(suite) {},
reportRunnerResults: function(runner) {
onTestRunConfigurationComplete();
}
};
jasmineEnv.execute();
return true;
},
onTestsFinish: function(){
jasmine.currentEnv_ = null;
rootDescribes.collectMode();
}
};
jstestdriver.pluginRegistrar.register(jasminePlugin);
function formatStack(stack) {
var lines = (stack||'').split(/\r?\n/);
var frames = [];
for (i = 0; i < lines.length; i++) {
if (!lines[i].match(/\/jasmine[\.-]/)) {
frames.push(lines[i].replace(/https?:\/\/\w+(:\d+)?\/test\//, '').replace(/^\s*/, ' '));
}
}
return frames.join('\n');
}
var currentFrame = frame(null, null);
function frame(parent, name){
var caseName = (parent && parent.caseName ? parent.caseName + " " : '') + (name ? name : '');
var frame = {
name: name,
caseName: caseName,
parent: parent,
testCase: TestCase(caseName),
before: [],
after: [],
runBefore: function(){
if (parent) parent.runBefore.apply(this);
for ( var i = 0; i < frame.before.length; i++) {
frame.before[i].apply(this);
}
},
runAfter: function(){
for ( var i = 0; i < frame.after.length; i++) {
frame.after[i].apply(this);
}
if (parent) parent.runAfter.apply(this);
}
};
return frame;
};
jasmine.Env.prototype.describe = (function(describe){
return function(description){
currentFrame = frame(currentFrame, description);
var val = describe.apply(this, arguments);
currentFrame = currentFrame.parent;
return val;
};
})(jasmine.Env.prototype.describe);
var id = 0;
jasmine.Env.prototype.it = (function(it){
return function(desc, itFn){
var self = this;
var spec = it.apply(this, arguments);
var currentSpec = this.currentSpec;
if (!currentSpec.$id) {
currentSpec.$id = id++;
}
var frame = this.jstdFrame = currentFrame;
var name = 'test that it ' + desc;
if (this.jstdFrame.testCase.prototype[name])
throw "Spec with name '" + desc + "' already exists.";
this.jstdFrame.testCase.prototype[name] = function(){
jasmine.getEnv().currentSpec = currentSpec;
frame.runBefore.apply(currentSpec);
try {
itFn.apply(currentSpec);
} finally {
frame.runAfter.apply(currentSpec);
function noop(){}
function Describes(window){
var describes = {};
var beforeEachs = {};
var afterEachs = {};
var exclusive;
var collectMode = true;
intercept('describe', describes);
intercept('xdescribe', describes);
intercept('beforeEach', beforeEachs);
intercept('afterEach', afterEachs);
function intercept(functionName, collection){
window[functionName] = function(desc, fn){
if (collectMode) {
collection[desc] = function(){
jasmine.getEnv()[functionName](desc, fn);
};
} else {
jasmine.getEnv()[functionName](desc, fn);
}
};
return spec;
}
window.ddescribe = function(name, fn){
exclusive = true;
console.log('ddescribe', name);
window.describe(name, function(){
var oldIt = window.it;
window.it = window.iit;
try {
fn.call(this);
} finally {
window.it = oldIt;
};
});
};
})(jasmine.Env.prototype.it);
jasmine.Env.prototype.beforeEach = (function(beforeEach){
return function(beforeEachFunction) {
beforeEach.apply(this, arguments);
currentFrame.before.push(beforeEachFunction);
window.iit = function(name, fn){
exclusive = fn.exclusive = true;
console.log(fn);
jasmine.getEnv().it(name, fn);
};
})(jasmine.Env.prototype.beforeEach);
jasmine.Env.prototype.afterEach = (function(afterEach){
return function(afterEachFunction) {
afterEach.apply(this, arguments);
currentFrame.after.push(afterEachFunction);
this.collectMode = function() {
collectMode = true;
exclusive = false;
};
})(jasmine.Env.prototype.afterEach);
jasmine.NestedResults.prototype.addResult = (function(addResult){
return function(result) {
addResult.call(this, result);
if (result.type != 'MessageResult' && !result.passed()) fail(result.message);
this.playback = function(){
collectMode = false;
playback(beforeEachs);
playback(afterEachs);
playback(describes);
function playback(set) {
for ( var name in set) {
set[name]();
}
}
};
this.isExclusive = function(spec) {
if (exclusive) {
var blocks = spec.queue.blocks;
for ( var i = 0; i < blocks.length; i++) {
if (blocks[i].func.exclusive) {
return true;
}
}
return false;
}
return true;
};
}
})(window);
})(jasmine.NestedResults.prototype.addResult);
// Patch Jasmine for proper stack traces
jasmine.Spec.prototype.fail = function (e) {
var expectationResult = new jasmine.ExpectationResult({
passed: false,
message: e ? jasmine.util.formatException(e) : 'Exception'
});
// PATCH
if (e) {
expectationResult.trace = e;
}
this.results_.addResult(expectationResult);
};
// Reset environment with overriden methods.
jasmine.currentEnv_ = null;
jasmine.getEnv();
})();
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
-21
View File
@@ -1,21 +0,0 @@
Copyright (c) 2009 Chris Wanstrath (Ruby)
Copyright (c) 2010 Jan Lehnardt (JavaScript)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-344
View File
@@ -1,344 +0,0 @@
/*
* CommonJS-compatible mustache.js module
*
* See http://github.com/janl/mustache.js for more info.
*/
/*
mustache.js Ñ Logic-less templates in JavaScript
See http://mustache.github.com/ for more info.
*/
var Mustache = function() {
var Renderer = function() {};
Renderer.prototype = {
otag: "{{",
ctag: "}}",
pragmas: {},
buffer: [],
pragmas_implemented: {
"IMPLICIT-ITERATOR": true
},
context: {},
render: function(template, context, partials, in_recursion) {
// reset buffer & set context
if(!in_recursion) {
this.context = context;
this.buffer = []; // TODO: make this non-lazy
}
// fail fast
if(!this.includes("", template)) {
if(in_recursion) {
return template;
} else {
this.send(template);
return;
}
}
template = this.render_pragmas(template);
var html = this.render_section(template, context, partials);
if(in_recursion) {
return this.render_tags(html, context, partials, in_recursion);
}
this.render_tags(html, context, partials, in_recursion);
},
/*
Sends parsed lines
*/
send: function(line) {
if(line != "") {
this.buffer.push(line);
}
},
/*
Looks for %PRAGMAS
*/
render_pragmas: function(template) {
// no pragmas
if(!this.includes("%", template)) {
return template;
}
var that = this;
var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
this.ctag);
return template.replace(regex, function(match, pragma, options) {
if(!that.pragmas_implemented[pragma]) {
throw({message:
"This implementation of mustache doesn't understand the '" +
pragma + "' pragma"});
}
that.pragmas[pragma] = {};
if(options) {
var opts = options.split("=");
that.pragmas[pragma][opts[0]] = opts[1];
}
return "";
// ignore unknown pragmas silently
});
},
/*
Tries to find a partial in the curent scope and render it
*/
render_partial: function(name, context, partials) {
name = this.trim(name);
if(!partials || partials[name] === undefined) {
throw({message: "unknown_partial '" + name + "'"});
}
if(typeof(context[name]) != "object") {
return this.render(partials[name], context, partials, true);
}
return this.render(partials[name], context[name], partials, true);
},
/*
Renders inverted (^) and normal (#) sections
*/
render_section: function(template, context, partials) {
if(!this.includes("#", template) && !this.includes("^", template)) {
return template;
}
var that = this;
// CSW - Added "+?" so it finds the tighest bound, not the widest
var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
"\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
"\\s*", "mg");
// for each {{#foo}}{{/foo}} section do...
return template.replace(regex, function(match, type, name, content) {
var value = that.find(name, context);
if(type == "^") { // inverted section
if(!value || that.is_array(value) && value.length === 0) {
// false or empty list, render it
return that.render(content, context, partials, true);
} else {
return "";
}
} else if(type == "#") { // normal section
if(that.is_array(value)) { // Enumerable, Let's loop!
return that.map(value, function(row) {
return that.render(content, that.create_context(row),
partials, true);
}).join("");
} else if(that.is_object(value)) { // Object, Use it as subcontext!
return that.render(content, that.create_context(value),
partials, true);
} else if(typeof value === "function") {
// higher order section
return value.call(context, content, function(text) {
return that.render(text, context, partials, true);
});
} else if(value) { // boolean section
return that.render(content, context, partials, true);
} else {
return "";
}
}
});
},
/*
Replace {{foo}} and friends with values from our view
*/
render_tags: function(template, context, partials, in_recursion) {
// tit for tat
var that = this;
var new_regex = function() {
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
that.ctag + "+", "g");
};
var regex = new_regex();
var tag_replace_callback = function(match, operator, name) {
switch(operator) {
case "!": // ignore comments
return "";
case "=": // set new delimiters, rebuild the replace regexp
that.set_delimiters(name);
regex = new_regex();
return "";
case ">": // render partial
return that.render_partial(name, context, partials);
case "{": // the triple mustache is unescaped
return that.find(name, context);
default: // escape the value
return that.escape(that.find(name, context));
}
};
var lines = template.split("\n");
for(var i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(regex, tag_replace_callback, this);
if(!in_recursion) {
this.send(lines[i]);
}
}
if(in_recursion) {
return lines.join("\n");
}
},
set_delimiters: function(delimiters) {
var dels = delimiters.split(" ");
this.otag = this.escape_regex(dels[0]);
this.ctag = this.escape_regex(dels[1]);
},
escape_regex: function(text) {
// thank you Simon Willison
if(!arguments.callee.sRE) {
var specials = [
'/', '.', '*', '+', '?', '|',
'(', ')', '[', ']', '{', '}', '\\'
];
arguments.callee.sRE = new RegExp(
'(\\' + specials.join('|\\') + ')', 'g'
);
}
return text.replace(arguments.callee.sRE, '\\$1');
},
/*
find `name` in current `context`. That is find me a value
from the view object
*/
find: function(name, context) {
name = this.trim(name);
// Checks whether a value is thruthy or false or 0
function is_kinda_truthy(bool) {
return bool === false || bool === 0 || bool;
}
var value = context;
var path = name.split(/\./);
for(var i = 0; i < path.length; i++) {
name = path[i];
if(value && is_kinda_truthy(value[name])) {
value = value[name];
} else if(i == 0 && is_kinda_truthy(this.context[name])) {
value = this.context[name];
} else {
value = undefined;
}
}
if(typeof value === "function") {
return value.apply(context);
}
if(value !== undefined) {
return value;
}
// silently ignore unkown variables
return "";
},
// Utility methods
/* includes tag */
includes: function(needle, haystack) {
return haystack.indexOf(this.otag + needle) != -1;
},
/*
Does away with nasty characters
*/
escape: function(s) {
s = String(s === null ? "" : s);
return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
switch(s) {
case "&": return "&amp;";
case "\\": return "\\\\";
case '"': return '&quot;';
case "'": return '&#39;';
case "<": return "&lt;";
case ">": return "&gt;";
default: return s;
}
});
},
// by @langalex, support for arrays of strings
create_context: function(_context) {
if(this.is_object(_context)) {
return _context;
} else {
var iterator = ".";
if(this.pragmas["IMPLICIT-ITERATOR"]) {
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
}
var ctx = {};
ctx[iterator] = _context;
return ctx;
}
},
is_object: function(a) {
return a && typeof a == "object";
},
is_array: function(a) {
return Object.prototype.toString.call(a) === '[object Array]';
},
/*
Gets rid of leading and trailing whitespace
*/
trim: function(s) {
return s.replace(/^\s*|\s*$/g, "");
},
/*
Why, why, why? Because IE. Cry, cry cry.
*/
map: function(array, fn) {
if (typeof array.map == "function") {
return array.map(fn);
} else {
var r = [];
var l = array.length;
for(var i = 0; i < l; i++) {
r.push(fn(array[i]));
}
return r;
}
}
};
return({
name: "mustache.js",
version: "0.3.1-dev",
/*
Turns a template and view into HTML
*/
to_html: function(template, view, partials, send_fun) {
var renderer = new Renderer();
if(send_fun) {
renderer.send = send_fun;
}
renderer.render(template, view, partials);
if(!send_fun) {
return renderer.buffer.join("\n");
}
}
});
}();
exports.name = Mustache.name;
exports.version = Mustache.version;
exports.to_html = function() {
return Mustache.to_html.apply(this, arguments);
};
+37
View File
@@ -0,0 +1,37 @@
<!doctype html>
<!--
This test demonstrates the time difference between document's DOMContentLoaded and window's load events.
-->
<html>
<head>
<script>
startTS = new Date().getTime();
onDOMContentLoadedTS = 0; // default for browsers where DOMCL is not supported
</script>
<title>DOMContentLoaded test</title>
<script src="../build/angular.min.js" ng:autobind></script>
<script>
angular.element(document).bind('DOMContentLoaded', function(e) {onDOMContentLoadedTS = new Date().getTime()});
angular.element(window).bind('load', function(e) {
onloadTS = new Date().getTime();
log.innerHTML = 'start: ' + new Date(startTS) + '<br/>DOMContentLoaded: +' + (onDOMContentLoadedTS - startTS) + 'ms<br/> load: +' + (onloadTS - startTS) + 'ms';
});
</script>
</head>
<body>
<h1>DOMContentLoaded test</h1>
<p>{{ 'yay!' || 'angular starting...' }}</p>
<img width="100px" src="http://lh5.ggpht.com/_BLyMhylclm0/TST_bbGH0zI/AAAAAAAAATY/oNUn9kivKN8/s912/1020047.jpg" />
<img width="100px" src="http://lh5.ggpht.com/_MqEybfAuUFk/TSOOiegUlPI/AAAAAAAADHY/AEwEWc64_-M/s800/IMG_7294.JPG" />
<img width="100px" src="http://lh3.ggpht.com/_LdjD3ua8rpE/TSOW99rwjZI/AAAAAAAAFC0/0qJRhhN45RM/s912/Saison%2010%20%2834%29.JPG" />
<img width="100px" src="http://lh6.ggpht.com/_oy_-am3CVUw/TSOQBddZpwI/AAAAAAAACaw/ogFgoD79bVE/s912/P1100886.JPG" />
<img width="100px" src="http://lh4.ggpht.com/_srSaA7ZN7oc/TDdxXbA_i1I/AAAAAAAAQ2w/ii3vgrnfCrM/s800/Urlaub10%20157.jpg" />
<img width="100px" src="http://lh5.ggpht.com/_y6vXu6iRrfM/SIaYhRQBYNI/AAAAAAAAAmE/lV2NYwxtsQM/s912/North%20Dakota%20Trip%20014.JPG" />
<img width="100px" src="http://lh5.ggpht.com/_Jjv9cIn9cS8/RuwZCgfOl6I/AAAAAAAAAOc/QrrMe8vpawg/s800/Shark%20Trip%20-%20day%202%20513.JPG" />
<p id="log"></p>
</body>
</html>
File diff suppressed because one or more lines are too long
+49
View File
@@ -0,0 +1,49 @@
def generate_object(f, objName, iterations)
f.write("var #{objName}='[");
iterations.times do |i|
f.write('{')
f.write('"simpleStringProperty":') #23
f.write('"some string value ' + ('%07d' % i) + '"') #27
f.write(',')
f.write('"stringWithQuotes":') #19
f.write('"some string with \\\\"quotes\\\\" ' + ('%07d' % i) + '"') #36
f.write(',')
f.write('"stringWithUnicode":')
f.write('"short string with \\u1234 unicode \\u2345 chars ' + ('%07d' % i) + '"')
f.write(',')
f.write('"aNumber":') #10
f.write(i) #?
f.write(',')
f.write('"smallArray":')
f.write('["a",23,"b",42,' + i.to_s + ']')
f.write(',')
f.write('"smallObj":')
f.write('{"foo":"bar","baz":543,"num":' + i.to_s + ',"fuz":"fuz buz huz duz ' + i.to_s + '"}')
f.write(',')
f.write('"timeStamp":')
f.write('"2010-12-22T04:58:01.' + ("%03d" % (i%1000)) + '"')
f.write('},')
end
f.write('"just a padding string"]\';' + "\n\n");
end
file_path = File.join(File.dirname(__FILE__), 'jsonParserPayload.js')
File.open(file_path, 'w') do |f|
generate_object(f, 'superTinyJsonString', 1) #~300b
generate_object(f, 'tinyJsonString', 3) #~1kb
generate_object(f, 'smallJsonString', 30) #~10kb
generate_object(f, 'mediumJsonString', 600) #~200kb
generate_object(f, 'largeJsonString', 2000) #~650kb
end
+28
View File
@@ -0,0 +1,28 @@
describe('json', function() {
it('angular parser', function() {
var duration = time(function() {
expect(angular.fromJson(largeJsonString)).toBeTruthy();
}, 1);
dump(duration/1 + ' ms per iteration');
});
it('angular delegating to native parser', function() {
var duration = time(function() {
expect(angular.fromJson(largeJsonString, true)).toBeTruthy();
}, 100);
dump(duration/100 + ' ms per iteration');
});
it('native json', function() {
var duration = time(function() {
expect(JSON.parse(largeJsonString)).toBeTruthy();
}, 100);
dump(duration/100 + ' ms per iteration');
});
});
+19
View File
@@ -0,0 +1,19 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns:ng="http://angularjs.org">
<head>
<script>
function el(id) {
return document.getElementById(id);
}
function update() {
el("output").innerHTML = el("input").value;
}
</script>
</head>
<body>
Your name: <input id="input" type="text" value="World"
onkeydown="setTimeout(update,0)"/>
<hr/>
Hello <span id="output">{{yourname}}</span>!
</body>
</html>
+20
View File
@@ -0,0 +1,20 @@
if (window.jstestdriver) {
jstd = jstestdriver;
dump = angular.bind(jstd.console, jstd.console.log);
}
function time(fn, times) {
times = times || 1;
var i,
start,
duration = 0;
for (i=0; i<times; i++) {
start = Date.now();
fn();
duration += Date.now() - start;
}
return duration;
}
+10
View File
@@ -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>
+28
View File
@@ -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}} &lt;-- 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}} &lt;-- 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}} &lt;-- 1 should be shown here
</body>
</html>
+8
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000 --config jsTestDriver-coverage.conf
+3 -1
View File
@@ -1 +1,3 @@
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000
#!/bin/bash
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 90000
+536 -353
View File
File diff suppressed because it is too large Load Diff
+19 -9
View File
@@ -1,17 +1,27 @@
var browserSingleton;
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$browser
* @requires $log
*
* @description
* Represents the browser.
*/
angularService('$browser', function($log){
if (!browserSingleton) {
browserSingleton = new Browser(
window.location,
jqLite(window.document),
jqLite(window.document.getElementsByTagName('head')[0]),
XHR,
$log);
browserSingleton.startPoller(50, function(delay, fn){setTimeout(delay,fn);});
browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body),
XHR, $log);
var addPollFn = browserSingleton.addPollFn;
browserSingleton.addPollFn = function(){
browserSingleton.addPollFn = addPollFn;
browserSingleton.startPoller(100, function(delay, fn){setTimeout(delay,fn);});
return addPollFn.apply(browserSingleton, arguments);
};
browserSingleton.bind();
}
return browserSingleton;
}, {inject:['$log']});
}, {$inject:['$log']});
extend(angular, {
'element': jqLite,
@@ -20,7 +30,7 @@ extend(angular, {
'copy': copy,
'extend': extend,
'equals': equals,
'foreach': foreach,
'forEach': forEach,
'injector': createInjector,
'noop':noop,
'bind':bind,
+251 -45
View File
@@ -8,8 +8,29 @@ var XHR = window.XMLHttpRequest || function () {
throw new Error("This browser does not support XMLHttpRequest.");
};
function Browser(location, document, head, XHR, $log) {
var self = this;
/**
* @private
* @name Browser
*
* @description
* Constructor for the object exposed as $browser service.
*
* This object has two goals:
*
* - hide all the global state in the browser caused by the window object
* - abstract away all the browser specific features and inconsistencies
*
* @param {object} window The global window object.
* @param {object} document jQuery wrapped document.
* @param {object} body jQuery wrapped document.body.
* @param {function()} XHR XMLHttpRequest constructor.
* @param {object} $log console.log or an object with the same interface.
*/
function Browser(window, document, body, XHR, $log) {
var self = this,
location = window.location,
setTimeout = window.setTimeout;
self.isMock = false;
//////////////////////////////////////////////////////////////
@@ -19,7 +40,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;
@@ -34,7 +91,7 @@ function Browser(location, document, head, XHR, $log) {
window[callbackId] = _undefined;
callback(200, data);
};
head.append(script);
body.append(script);
} else {
var xhr = new XHR();
xhr.open(method, url, true);
@@ -44,26 +101,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 +128,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 +180,74 @@ 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;
};
};
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#onHashChange
* @methodOf angular.service.$browser
*
* @description
* Detects if browser support onhashchange events and register a listener otherwise registers
* $browser poller. The `listener` will then get called when the hash changes.
*
* The listener gets called with either HashChangeEvent object or simple object that also contains
* `oldURL` and `newURL` properties.
*
* NOTE: this is a api is intended for sole use by $location service. Please use
* {@link angular.service.$location $location service} to monitor hash changes in angular apps.
*
* @param {function(event)} listener Listener function to be called when url hash changes.
* @return {function()} Returns the registered listener fn - handy if the fn is anonymous.
*/
self.onHashChange = function(listener) {
if ('onhashchange' in window) {
jqLite(window).bind('hashchange', listener);
} else {
var lastBrowserUrl = self.getUrl();
self.addPollFn(function() {
if (lastBrowserUrl != self.getUrl()) {
listener();
}
});
}
return listener;
}
//////////////////////////////////////////////////////////////
// Cookies API
@@ -121,19 +257,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 +316,56 @@ function Browser(location, document, head, XHR, $log) {
}
};
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#defer
* @methodOf angular.service.$browser
* @param {function()} fn A function, who's execution should be defered.
* @param {int=} [delay=0] of milliseconds to defer the function execution.
*
* @description
* Executes a fn asynchroniously via `setTimeout(fn, delay)`.
*
* 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()`.
*
*/
self.defer = function(fn, delay) {
outstandingRequestCount++;
setTimeout(function() { completeOutstandingRequest(fn); }, delay || 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,25 +379,41 @@ 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');
link.attr('href', url);
head.append(link);
body.append(link);
};
/**
* @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);
if (dom_id) script.attr('id', dom_id);
head.append(script);
body.append(script);
};
}
+74 -8
View File
@@ -16,8 +16,8 @@ Template.prototype = {
init: function(element, scope) {
var inits = {};
this.collectInits(element, inits, scope);
foreachSorted(inits, function(queue){
foreach(queue, function(fn) {fn();});
forEachSorted(inits, function(queue){
forEach(queue, function(fn) {fn();});
});
},
@@ -30,11 +30,12 @@ Template.prototype = {
if (this.newScope) {
childScope = createScope(scope);
scope.$onEval(childScope.$eval);
element.data($$scope, childScope);
}
foreach(this.inits, function(fn) {
forEach(this.inits, function(fn) {
queue.push(function() {
childScope.$tryEval(function(){
return childScope.$inject(fn, childScope, element);
return childScope.$service(fn, childScope, element);
}, element);
});
});
@@ -68,6 +69,18 @@ Template.prototype = {
}
};
/*
* Function walks up the element chain looking for the scope associated with the give element.
*/
function retrieveScope(element) {
var scope;
element = jqLite(element);
while (element && !(scope = element.data($$scope))) {
element = element.parent();
}
return scope;
}
///////////////////////////////////
//Compiler
//////////////////////////////////
@@ -97,6 +110,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 +123,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,
@@ -116,7 +182,7 @@ Compiler.prototype = {
directiveFns = self.directives,
descend = true,
directives = true,
elementName = nodeName(element),
elementName = nodeName_(element),
template,
selfApi = {
compile: bind(self, self.compile),
@@ -166,7 +232,7 @@ Compiler.prototype = {
for(var i=0, child=element[0].childNodes;
i<child.length; i++) {
if (isTextNode(child[i])) {
foreach(self.markup, function(markup){
forEach(self.markup, function(markup){
if (i<child.length) {
var textNode = jqLite(child[i]);
markup.call(selfApi, textNode.text(), textNode, element);
@@ -179,7 +245,7 @@ Compiler.prototype = {
if (directives) {
// Process attributes/directives
eachAttribute(element, function(value, name){
foreach(self.attrMarkup, function(markup){
forEach(self.attrMarkup, function(markup){
markup.call(selfApi, value, name, element);
});
});
@@ -221,6 +287,6 @@ function eachAttribute(element, fn){
}
attrValue[name] = value;
}
foreachSorted(attrValue, fn);
forEachSorted(attrValue, fn);
}
+36 -17
View File
@@ -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,13 +30,14 @@ 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.
*/
return function inject(value, scope, args){
var returnValue, provider, creation;
var returnValue, provider;
if (isString(value)) {
if (!cache.hasOwnProperty(value)) {
provider = providers[value];
@@ -35,25 +47,32 @@ function createInjector(providerScope, providers, cache) {
returnValue = cache[value];
} else if (isArray(value)) {
returnValue = [];
foreach(value, function(name) {
forEach(value, function(name) {
returnValue.push(inject(name));
});
} else if (isFunction(value)) {
returnValue = inject(value.$inject || []);
returnValue = value.apply(scope, concat(returnValue, arguments, 2));
} else if (isObject(value)) {
foreach(providers, function(provider, name){
creation = provider.$creation;
if (creation == 'eager') {
forEach(providers, function(provider, name){
if (provider.$eager)
inject(name);
}
if (creation == 'eager-published') {
setter(value, name, inject(name));
}
if (provider.$creation)
throw new Error("Failed to register service '" + name +
"': $creation property is unsupported. Use $eager:true or see release notes.");
});
} else {
returnValue = inject(providerScope);
}
return returnValue;
};
}
}
function injectService(services, fn) {
return extend(fn, {$inject:services});;
}
function injectUpdateView(fn) {
return injectService(['$updateView'], fn);
}
+50 -3
View File
@@ -1,22 +1,69 @@
var array = [].constructor;
/**
* @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('');
}
function fromJson(json) {
/**
* @workInProgress
* @ngdoc function
* @name angular.fromJson
* @function
*
* @description
* Deserializes a string in the JSON format.
*
* @param {string} json JSON string to deserialize.
* @param {boolean} [useNative=false] Use native JSON parser if available
* @returns {Object|Array|Date|string|number} Deserialized thingy.
*/
function fromJson(json, useNative) {
if (!json) return json;
var obj, p, expression;
try {
var p = parser(json, true);
var expression = p.primary();
if (useNative && JSON && JSON.parse) {
obj = JSON.parse(json);
return transformDates(obj);
}
p = parser(json, true);
expression = p.primary();
p.assertAllConsumed();
return expression();
} catch (e) {
error("fromJson error: ", json, e);
throw e;
}
// TODO make forEach optionally recursive and remove this function
function transformDates(obj) {
if (isString(obj) && obj.length === DATE_ISOSTRING_LN) {
return angularString.toDate(obj);
} else if (isArray(obj) || isObject(obj)) {
forEach(obj, function(val, name) {
obj[name] = transformDates(val);
});
}
return obj;
}
}
angular['toJson'] = toJson;
+7 -6
View File
@@ -4,7 +4,7 @@ function Route(template, defaults) {
this.template = template = template + '#';
this.defaults = defaults || {};
var urlParams = this.urlParams = {};
foreach(template.split(/\W/), function(param){
forEach(template.split(/\W/), function(param){
if (param && template.match(new RegExp(":" + param + "\\W"))) {
urlParams[param] = true;
}
@@ -17,13 +17,13 @@ Route.prototype = {
var self = this;
var url = this.template;
params = params || {};
foreach(this.urlParams, function(_, urlParam){
forEach(this.urlParams, function(_, urlParam){
var value = params[urlParam] || self.defaults[urlParam] || "";
url = url.replace(new RegExp(":" + urlParam + "(\\W)"), value + "$1");
});
url = url.replace(/\/?#$/, '');
var query = [];
foreachSorted(params, function(value, key){
forEachSorted(params, function(value, key){
if (!self.urlParams[key]) {
query.push(encodeURI(key) + '=' + encodeURI(value));
}
@@ -52,7 +52,7 @@ ResourceFactory.prototype = {
actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions);
function extractParams(data){
var ids = {};
foreach(paramDefaults || {}, function(value, key){
forEach(paramDefaults || {}, function(value, key){
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
});
return ids;
@@ -62,7 +62,7 @@ ResourceFactory.prototype = {
copy(value || {}, this);
}
foreach(actions, function(action, name){
forEach(actions, function(action, name){
var isPostOrPut = action.method == 'POST' || action.method == 'PUT';
Resource[name] = function (a1, a2, a3) {
var params = {};
@@ -73,6 +73,7 @@ ResourceFactory.prototype = {
case 2:
if (isFunction(a2)) {
callback = a2;
//fallthrough
} else {
params = a1;
data = a2;
@@ -97,7 +98,7 @@ ResourceFactory.prototype = {
if (status == 200) {
if (action.isArray) {
value.length = 0;
foreach(response, function(item){
forEach(response, function(item){
value.push(new Resource(item));
});
} else {
+423 -35
View File
@@ -48,7 +48,7 @@ var scopeId = 0,
getterFnCache = {},
compileCache = {},
JS_KEYWORDS = {};
foreach(
forEach(
("abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default," +
"delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto," +
"if,implements,import,ininstanceof,intinterface,long,native,new,null,package,private," +
@@ -61,12 +61,12 @@ function getterFn(path){
if (fn) return fn;
var code = 'var l, fn, t;\n';
foreach(path.split('.'), function(key) {
forEach(path.split('.'), function(key) {
key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
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,27 +543,42 @@ 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;
foreach(Class.prototype, function(fn, name){
forEach(Class.prototype, function(fn, name){
instance[name] = bind(instance, fn);
});
instance.$inject.apply(instance, concat([Class, instance], arguments, 1));
instance.$service.apply(instance, concat([Class, instance], arguments, 1));
//TODO: backwards compatibility hack, remove when we don't depend on init methods
if (isFunction(Class.prototype.init)) {
@@ -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;
}
@@ -243,7 +615,23 @@ function createScope(parent, providers, instanceCache) {
if (!parent.$root) {
instance.$root = instance;
instance.$parent = instance;
(instance.$inject = createInjector(instance, providers, instanceCache))();
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$service
* @function
*
* @description
* Provides access to angular's dependency injector and
* {@link angular.service registered services}. In general the use of this api is discouraged,
* except for tests and components that currently don't support dependency injection (widgets,
* filters, etc).
*
* @param {string} serviceId String ID of the service to return.
* @returns {*} Value, object or function returned by the service factory function if any.
*/
(instance.$service = createInjector(instance, providers, instanceCache))();
}
return instance;
+95 -33
View File
@@ -21,11 +21,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function(previousOnLoad){
var filename = /^(.*)\/angular-bootstrap.js(#.*)?$/,
(function(window) {
var filename = /^(.*\/)angular-bootstrap.js(#.*)?$/,
scripts = document.getElementsByTagName("SCRIPT"),
serverPath,
match;
match,
globalVars = {};
for(var j = 0; j < scripts.length; j++) {
match = (scripts[j].src || "").match(filename);
if (match) {
@@ -33,50 +36,109 @@
}
}
function addScript(file){
document.write('<script type="text/javascript" src="' + serverPath + file +'"></script>');
function key(prop) {
return "ng-clobber_" + prop;
}
window.angularClobberTest = function(file) {
var varKey, prop,
clobbered = [];
for (prop in window) {
varKey = key(prop);
if (prop === 'event') { //skip special variables which keep on changing
continue;
}
else if (!globalVars.hasOwnProperty(varKey)) {
//console.log('new global variable found: ', prop);
globalVars[varKey] = window[prop];
} else if (globalVars[varKey] !== window[prop] && !isActuallyNaN(window[prop])) {
clobbered.push(prop);
console.error("Global variable clobbered by script " + file + "! Variable name: " + prop);
globalVars[varKey] = window[prop];
}
}
for (varKey in globalVars) {
prop = varKey.substr(11);
if (clobbered.indexOf(prop) == -1 &&
prop != 'event' &&
!isActuallyNaN(globalVars[varKey]) &&
globalVars[varKey] !== window[prop]) {
delete globalVars[varKey];
console.warn("Global variable unexpectedly deleted in script " + file + "! " +
"Variable name: " + prop);
}
}
function isActuallyNaN(val) {
return isNaN(val) && (typeof val === 'number');
}
}
function addScripts(){
var prop, i;
// initialize the window property cache
for (prop in window) {
globalVars[key(prop)] = window[prop];
}
// load the js scripts
for (i in arguments) {
file = arguments[i];
document.write('<script type="text/javascript" src="' + serverPath + file + '" ' +
'onload="angularClobberTest(\'' + file + '\')"></script>');
}
}
function addCss(file) {
document.write('<link rel="stylesheet" type="text/css" href="' +
serverPath + '/../css' + file + '"/>');
serverPath + '../css/' + file + '"/>');
}
addCss("/angular.css");
addCss('angular.css');
addScript("/Angular.js");
addScript("/JSON.js");
addScript("/Compiler.js");
addScript("/Scope.js");
addScript("/Injector.js");
addScript("/jqLite.js");
addScript("/parser.js");
addScript("/Resource.js");
addScript("/Browser.js");
addScript("/sanitizer.js");
addScript("/AngularPublic.js");
addScripts('Angular.js',
'JSON.js',
'Compiler.js',
'Scope.js',
'Injector.js',
'jqLite.js',
'parser.js',
'Resource.js',
'Browser.js',
'sanitizer.js',
'AngularPublic.js',
// Extension points
addScript("/services.js");
addScript("/apis.js");
addScript("/filters.js");
addScript("/formatters.js");
addScript("/validators.js");
addScript("/directives.js");
addScript("/markups.js");
addScript("/widgets.js");
// Extension points
'services.js',
'apis.js',
'filters.js',
'formatters.js',
'validators.js',
'directives.js',
'markups.js',
'widgets.js');
window.onload = function(){
try {
if (previousOnLoad) previousOnLoad();
} catch(e) {}
function onLoadListener(){
// empty the cache to prevent mem leaks
globalVars = {};
//angular-ie-compat.js needs to be pregenerated for development with IE<8
if (msie<8) addScript('../angular-ie-compat.js');
angularInit(angularJsConfig(document));
};
}
})(window.onload);
if (window.addEventListener){
window.addEventListener('load', onLoadListener, false);
} else if (window.attachEvent){
window.attachEvent('onload', onLoadListener);
}
})(window);
+1 -1
View File
@@ -21,4 +21,4 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function(window, document, previousOnLoad){
(function(window, document){
+3 -6
View File
@@ -1,9 +1,6 @@
window.onload = function(){
try {
if (previousOnLoad) previousOnLoad();
} catch(e) {}
jqLite(document).ready(function(){
angularInit(angularJsConfig(document));
};
});
})(window, document, window.onload);
})(window, document);
+498 -3
View File
@@ -11,6 +11,26 @@ var angularGlobal = {
}
};
/**
* @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,20 +378,214 @@ 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&gt;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;
foreach(array, function(value){
forEach(array, function(value){
if (fn(value)) {
count ++;
}
});
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,11 +628,51 @@ 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 R_ISO8061_STR = /^(\d{4})-(\d\d)-(\d\d)(?:T(\d\d)(?:\:(\d\d)(?:\:(\d\d)(?:\.(\d{3}))?)?)?Z)?$/;
var angularString = {
'quote':function(string) {
@@ -252,7 +747,7 @@ var angularFunction = {
function defineApi(dst, chain){
angular[dst] = angular[dst] || {};
foreach(chain, function(parent){
forEach(chain, function(parent){
extend(angular[dst], parent);
});
}
+53 -173
View File
@@ -1,4 +1,5 @@
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:init
*
@@ -27,6 +28,7 @@ angularDirective("ng:init", function(expression){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:controller
*
@@ -105,6 +107,7 @@ angularDirective("ng:controller", function(expression){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:eval
*
@@ -147,6 +150,7 @@ angularDirective("ng:eval", function(expression){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:bind
*
@@ -155,7 +159,7 @@ angularDirective("ng:eval", function(expression){
* 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.
* `<span ng:bind="expression"></span>` at bootstrap time.
*
* @element ANY
* @param {expression} expression to eval.
@@ -182,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
@@ -218,7 +222,7 @@ function compileBindTemplate(template){
var fn = bindTemplateCache[template];
if (!fn) {
var bindings = [];
foreach(parseBindings(template), function(text){
forEach(parseBindings(template), function(text){
var exp = binding(text);
bindings.push(exp ? function(element){
var error, value = this.$tryEval(exp, function(e){
@@ -250,6 +254,7 @@ function compileBindTemplate(template){
}
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:bind-template
*
@@ -299,9 +304,11 @@ 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
*
@@ -313,7 +320,7 @@ var REMOVE_ATTRIBUTES = {
* `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
* translated into `<span ng:bind-attr="{attr:expression}"/>` at
* bootstrap time.
*
* This HTML snippet is preferred way of working with `ng:bind-attr`
@@ -353,187 +360,38 @@ var REMOVE_ATTRIBUTES = {
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);
};
});
/**
* @ngdoc directive
* @name angular.directive.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 ignore a chunk of HTML.
*
* @element ANY
* @param {string} ignore
*
* @exampleDescription
* In this example there are two location where
* <tt ng:non-bindable>{{1 + 2}}</tt> 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);
/**
* @ngdoc directive
* @name angular.directive.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.
*
* NOTE: `ng:repeat` looks like a directive, but is actually a
* attribute widget.
*
* @element ANY
* @param {repeat} repeat_expression to itterate over.
*
* * `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}`.
*
* Special properties set on the local scope:
* * {number} $index - iterator offset of the repeated element (0..length-1)
* * {string} $position - position of the repeated element in the iterator ('first', 'middle', 'last')
*
* @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 "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,
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 directive
* @name angular.directive.ng:click
*
@@ -565,18 +423,19 @@ angularWidget("@ng:repeat", function(expression, element){
* TODO: maybe we should consider allowing users to control event propagation in the future.
*/
angularDirective("ng:click", function(expression, element){
return function(element){
return injectUpdateView(function($updateView, element){
var self = this;
element.bind('click', function(event){
self.$tryEval(expression, element);
self.$root.$eval();
$updateView();
event.stopPropagation();
});
};
});
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:submit
*
@@ -612,18 +471,19 @@ angularDirective("ng:click", function(expression, element){
* server and reloading the current page).
*/
angularDirective("ng:submit", function(expression, element) {
return function(element) {
return injectUpdateView(function($updateView, element) {
var self = this;
element.bind('submit', function(event) {
self.$tryEval(expression, element);
self.$root.$eval();
$updateView();
event.preventDefault();
});
};
});
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:watch
*
@@ -649,6 +509,7 @@ angularDirective("ng:submit", function(expression, element) {
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;
@@ -678,6 +539,7 @@ function ngClass(selector) {
}
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:class
*
@@ -714,6 +576,7 @@ function ngClass(selector) {
angularDirective("ng:class", ngClass(function(){return true;}));
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:class-odd
*
@@ -749,6 +612,7 @@ angularDirective("ng:class", ngClass(function(){return true;}));
angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:class-even
*
@@ -784,6 +648,7 @@ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:show
*
@@ -821,6 +686,7 @@ angularDirective("ng:show", function(expression, element){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:hide
*
@@ -858,19 +724,33 @@ 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 to eval.
* @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){
+20 -6
View File
@@ -1,4 +1,5 @@
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.currency
* @function
@@ -33,6 +34,7 @@ angularFilter.currency = function(amount){
};
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.number
* @function
@@ -42,8 +44,8 @@ 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
@@ -148,6 +150,7 @@ var NUMBER_STRING = /^\d+$/;
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.date
* @function
@@ -214,12 +217,18 @@ angularFilter.date = function(date, format) {
var text = date.toLocaleDateString(), fn;
if (format && isString(format)) {
text = '';
var parts = [];
var parts = [], match;
while(format) {
parts = concat(parts, DATE_FORMATS_SPLIT.exec(format), 1);
format = parts.pop();
match = DATE_FORMATS_SPLIT.exec(format);
if (match) {
parts = concat(parts, match, 1);
format = parts.pop();
} else {
parts.push(format);
format = null;
}
}
foreach(parts, function(value){
forEach(parts, function(value){
fn = DATE_FORMATS[value];
text += fn ? fn(date) : value;
});
@@ -229,6 +238,7 @@ angularFilter.date = function(date, format) {
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.json
* @function
@@ -267,6 +277,7 @@ angularFilter.json = function(object) {
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.lowercase
* @function
@@ -277,6 +288,7 @@ angularFilter.lowercase = lowercase;
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.uppercase
* @function
@@ -287,6 +299,7 @@ angularFilter.uppercase = uppercase;
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.html
* @function
@@ -375,6 +388,7 @@ angularFilter.html = function(html, option){
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.linky
* @function
+10 -5
View File
@@ -8,6 +8,7 @@ var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/;
angularFormatter.noop = formatter(identity, identity);
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.json
*
@@ -32,13 +33,14 @@ angularFormatter.noop = formatter(identity, identity);
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 Convert to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
* @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
*
* @example
* Enter truthy text:
@@ -56,13 +58,14 @@ angularFormatter.json = formatter(toJson, fromJson);
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 parse string to number.
* @returns {number} Number from the parsed string.
*
* @example
* Enter valid number:
@@ -85,13 +88,14 @@ angularFormatter.number = formatter(toString, function(obj){
});
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.list
*
* @description
* Use number formatter if you wish to convert the user entered string to a number.
* Use list formatter if you wish to convert the user entered string to an array.
*
* @returns parse string to number.
* @returns {Array} Array parsed from the entered string.
*
* @example
* Enter a list of items:
@@ -113,7 +117,7 @@ angularFormatter.list = formatter(
function(obj) { return obj ? obj.join(", ") : obj; },
function(value) {
var list = [];
foreach((value || '').split(','), function(item){
forEach((value || '').split(','), function(item){
item = trim(item);
if (item) list.push(item);
});
@@ -122,6 +126,7 @@ angularFormatter.list = formatter(
);
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.trim
*
+31 -18
View File
@@ -5,12 +5,12 @@
var jqCache = {},
jqName = 'ng-' + new Date().getTime(),
jqId = 1,
addEventListener = (window.document.attachEvent ?
function(element, type, fn) {element.attachEvent('on' + type, fn);} :
function(element, type, fn) {element.addEventListener(type, fn, false);}),
removeEventListener = (window.document.detachEvent ?
function(element, type, fn) {element.detachEvent('on' + type, fn); } :
function(element, type, fn) { element.removeEventListener(type, fn, false); });
addEventListenerFn = (window.document.addEventListener ?
function(element, type, fn) {element.addEventListener(type, fn, false);} :
function(element, type, fn) {element.attachEvent('on' + type, fn);}),
removeEventListenerFn = (window.document.removeEventListener ?
function(element, type, fn) {element.removeEventListener(type, fn, false); } :
function(element, type, fn) {element.detachEvent('on' + type, fn); });
function jqNextId() { return (jqId++); }
@@ -18,8 +18,8 @@ function jqClearData(element) {
var cacheId = element[jqName],
cache = jqCache[cacheId];
if (cache) {
foreach(cache.bind || {}, function(fn, type){
removeEventListener(element, type, fn);
forEach(cache.bind || {}, function(fn, type){
removeEventListenerFn(element, type, fn);
});
delete jqCache[cacheId];
if (msie)
@@ -47,14 +47,14 @@ function getStyle(element) {
}
function JQLite(element) {
if (isElement(element)) {
this[0] = element;
this.length = 1;
} else if (isDefined(element.length) && element.item) {
if (!isElement(element) && isDefined(element.length) && element.item && !isWindow(element)) {
for(var i=0; i < element.length; i++) {
this[i] = element[i];
}
this.length = element.length;
} else {
this[0] = element;
this.length = 1;
}
}
@@ -81,19 +81,32 @@ JQLite.prototype = {
dealoc: function(){
(function dealoc(element){
jqClearData(element);
for ( var i = 0, children = element.childNodes; i < children.length; i++) {
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
dealoc(children[i]);
}
})(this[0]);
},
ready: function(fn) {
var fired = false;
function trigger() {
if (fired) return;
fired = true;
fn();
}
this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
jqLite(window).bind('load', trigger); // fallback to window.onload for others
},
bind: function(type, fn){
var self = this,
element = self[0],
bind = self.data('bind'),
eventHandler;
if (!bind) this.data('bind', bind = {});
foreach(type.split(' '), function(type){
forEach(type.split(' '), function(type){
eventHandler = bind[type];
if (!eventHandler) {
bind[type] = eventHandler = function(event) {
@@ -107,12 +120,12 @@ JQLite.prototype = {
event.cancelBubble = true; //ie
};
}
foreach(eventHandler.fns, function(fn){
forEach(eventHandler.fns, function(fn){
fn.call(self, event);
});
};
eventHandler.fns = [];
addEventListener(element, type, eventHandler);
addEventListenerFn(element, type, eventHandler);
}
eventHandler.fns.push(fn);
});
@@ -129,7 +142,7 @@ JQLite.prototype = {
append: function(node) {
var self = this[0];
node = jqLite(node);
foreach(node, function(child){
forEach(node, function(child){
self.appendChild(child);
});
},
@@ -187,7 +200,7 @@ JQLite.prototype = {
attr: function(name, value){
var e = this[0];
if (isObject(name)) {
foreach(name, function(value, name){
forEach(name, function(value, name){
e.setAttribute(name, value);
});
} else if (isDefined(value)) {
+55 -2
View File
@@ -35,7 +35,7 @@ angularTextMarkup('{{}}', function(text, textNode, parentElement) {
parentElement.attr('ng:bind-template', text);
} else {
var cursor = textNode, newElement;
foreach(parseBindings(text), function(text){
forEach(parseBindings(text), function(text){
var exp = binding(text);
if (exp) {
newElement = self.element('span');
@@ -59,7 +59,7 @@ angularTextMarkup('{{}}', function(text, textNode, parentElement) {
// TODO: this should be widget not a markup
angularTextMarkup('OPTION', function(text, textNode, parentElement){
if (nodeName(parentElement) == "OPTION") {
if (nodeName_(parentElement) == "OPTION") {
var select = document.createElement('select');
select.insertBefore(parentElement[0].cloneNode(true), _null);
if (!select.innerHTML.match(/<option(\s.*\s|\s)value\s*=\s*.*>.*<\/\s*option\s*>/gi)) {
@@ -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){
+98 -117
View File
@@ -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,7 +26,7 @@ var OPERATORS = {
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
function lex(text, parseStringsForObjects){
var dateParseLength = parseStringsForObjects ? 24 : -1,
var dateParseLength = parseStringsForObjects ? DATE_ISOSTRING_LN : -1,
tokens = [],
token,
index = 0,
@@ -40,16 +40,19 @@ function lex(text, parseStringsForObjects){
readString(ch);
} else if (isNumber(ch) || is('.') && isNumber(peek())) {
readNumber();
} else if ( was('({[:,;') && is('/') ) {
readRegexp();
} else if (isIdent(ch)) {
readIdent();
// identifiers can only be if the preceding char was a { or ,
if (was('{,') && json[0]=='{' &&
(token=tokens[tokens.length-1])) {
token.json = token.text.indexOf('.') == -1;
}
} else if (is('(){}[].,;:')) {
tokens.push({index:index, text:ch, json:is('{}[]:,')});
tokens.push({
index:index,
text:ch,
json:(was(':[,') && is('{[')) || is('}]:,')
});
if (is('{[')) json.unshift(ch);
if (is('}]')) json.shift();
index++;
@@ -67,10 +70,7 @@ 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;
@@ -103,6 +103,16 @@ function lex(text, parseStringsForObjects){
function isExpOperator(ch) {
return ch == '-' || ch == '+' || isNumber(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 = "";
var start = index;
@@ -121,7 +131,7 @@ function lex(text, parseStringsForObjects){
} else if (isExpOperator(ch) &&
(!peekCh || !isNumber(peekCh)) &&
number.charAt(number.length - 1) == 'e') {
throw 'Lexer found invalid exponential value "' + text + '"';
throwError('Invalid exponent');
} else {
break;
}
@@ -135,6 +145,7 @@ function lex(text, parseStringsForObjects){
function readIdent() {
var ident = "";
var start = index;
var fn;
while (index < text.length) {
var ch = text.charAt(index);
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
@@ -144,14 +155,19 @@ function lex(text, parseStringsForObjects){
}
index++;
}
var fn = OPERATORS[ident];
if (!fn) {
fn = getterFn(ident);
fn.isAssignable = ident;
}
tokens.push({index:start, text:ident, fn:fn, json: OPERATORS[ident]});
fn = OPERATORS[ident];
tokens.push({
index:start,
text:ident,
json: fn,
fn:fn||extend(getterFn(ident), {
assign:function(self, value){
return setter(self, ident, value);
}
})
});
}
function readString(quote) {
var start = index;
index++;
@@ -165,9 +181,7 @@ function lex(text, parseStringsForObjects){
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 + "'.";
throwError( "Invalid unicode escape [\\u" + hex + "]");
index += 4;
string += String.fromCharCode(parseInt(hex, 16));
} else {
@@ -194,42 +208,7 @@ function lex(text, parseStringsForObjects){
}
index++;
}
throw "Lexer Error: Unterminated quote [" +
text.substring(start) + "] starting at column '" +
(start+1) + "' in expression '" + text + "'.";
}
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;
}
index++;
}
throw "Lexer Error: Unterminated RegExp [" +
text.substring(start) + "] starting at column '" +
(start+1) + "' in expression '" + text + "'.";
throwError("Unterminated quote", start);
}
}
@@ -237,28 +216,47 @@ function lex(text, parseStringsForObjects){
function parser(text, json){
var ZERO = valueFn(0),
tokens = lex(text, json);
tokens = lex(text, json),
assignment = _assignment,
functionCall = _functionCall,
fieldAccess = _fieldAccess,
objectIndex = _objectIndex,
filterChain = _filterChain,
functionIdent = _functionIdent,
pipeFunction = _pipeFunction;
if(json){
// The extra level of aliasing is here, just in case the lexer misses something, so that
// we prevent any accidental execution in JSON.
assignment = logicalOR;
functionCall =
fieldAccess =
objectIndex =
filterChain =
functionIdent =
pipeFunction =
function (){ throwError("is not valid json", {text:text, index:0}); };
}
return {
assertAllConsumed: assertAllConsumed,
primary: primary,
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 +277,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 +288,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 +310,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]);
}
}
@@ -343,7 +333,7 @@ function parser(text, json){
}
}
function filterChain(){
function _filterChain(){
var left = expression();
var token;
while(true) {
@@ -363,7 +353,7 @@ function parser(text, json){
return pipeFunction(angularValidator);
}
function pipeFunction(fnScope){
function _pipeFunction(fnScope){
var fn = functionIdent(fnScope);
var argsFn = [];
var token;
@@ -386,31 +376,22 @@ function parser(text, json){
}
function expression(){
return throwStmt();
return assignment();
}
function throwStmt(){
if (expect('throw')) {
var throwExp = assignment();
return function (self) {
throw throwExp(self);
};
} else {
return assignment();
}
}
function 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;
}
@@ -486,7 +467,7 @@ function parser(text, json){
}
}
function functionIdent(fnScope) {
function _functionIdent(fnScope) {
var token = expect();
var element = token.text.split('.');
var instance = fnScope;
@@ -497,8 +478,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 +497,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,40 +509,40 @@ function parser(text, json){
} else if (next.text === '.') {
primary = fieldAccess(primary);
} else {
throw "IMPOSSIBLE";
throwError("IMPOSSIBLE");
}
}
return primary;
}
function fieldAccess(object) {
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) {
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) {
function _functionCall(fn) {
var argsFn = [];
if (peekToken().text != ')') {
do {
@@ -624,6 +604,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()) {
+67 -72
View File
@@ -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 = /&nbsp;/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,"&lt;");
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, '&amp;').
replace(NON_ALPHANUMERIC_REGEXP, function(value){
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
@@ -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)) {
forEach(attrs, function(value, key){
var lkey=lowercase(key);
if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
out(' ');
out(key);
out('="');
out(value.
replace(/</g, '&lt;').
replace(/>/g, '&gt;').
replace(/\"/g,'&quot;'));
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:'&amp;';}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;'));
out(encodeEntities(chars));
}
}
};
+5 -5
View File
@@ -21,7 +21,7 @@ angular.scenario.Describe = function(descName, parent) {
var beforeEachFns = this.beforeEachFns;
this.setupBefore = function() {
if (parent) parent.setupBefore.call(this);
angular.foreach(beforeEachFns, function(fn) { fn.call(this); }, this);
angular.forEach(beforeEachFns, function(fn) { fn.call(this); }, this);
};
/**
@@ -29,7 +29,7 @@ angular.scenario.Describe = function(descName, parent) {
*/
var afterEachFns = this.afterEachFns;
this.setupAfter = function() {
angular.foreach(afterEachFns, function(fn) { fn.call(this); }, this);
angular.forEach(afterEachFns, function(fn) { fn.call(this); }, this);
if (parent) parent.setupAfter.call(this);
};
};
@@ -133,14 +133,14 @@ angular.scenario.Describe.prototype.xit = angular.noop;
*/
angular.scenario.Describe.prototype.getSpecs = function() {
var specs = arguments[0] || [];
angular.foreach(this.children, function(child) {
angular.forEach(this.children, function(child) {
child.getSpecs(specs);
});
angular.foreach(this.its, function(it) {
angular.forEach(this.its, function(it) {
specs.push(it);
});
var only = [];
angular.foreach(specs, function(it) {
angular.forEach(specs, function(it) {
if (it.only) {
only.push(it);
}
+1 -1
View File
@@ -19,7 +19,7 @@ angular.scenario.ObjectModel = function(runner) {
runner.on('SpecBegin', function(spec) {
var block = self.value;
angular.foreach(self.getDefinitionPath(spec), function(def) {
angular.forEach(self.getDefinitionPath(spec), function(def) {
if (!block.children[def.name]) {
block.children[def.name] = {
id: def.id,
+5 -5
View File
@@ -16,7 +16,7 @@ angular.scenario.Runner = function($window) {
beforeEach: this.beforeEach,
afterEach: this.afterEach
};
angular.foreach(this.api, angular.bind(this, function(fn, key) {
angular.forEach(this.api, angular.bind(this, function(fn, key) {
this.$window[key] = angular.bind(this, fn);
}));
};
@@ -33,7 +33,7 @@ angular.scenario.Runner.prototype.emit = function(eventName) {
eventName = eventName.toLowerCase();
if (!this.listeners[eventName])
return;
angular.foreach(this.listeners[eventName], function(listener) {
angular.forEach(this.listeners[eventName], function(listener) {
listener.apply(self, args);
});
};
@@ -164,17 +164,17 @@ angular.scenario.Runner.prototype.run = function(application) {
asyncForEach(this.rootDescribe.getSpecs(), function(spec, specDone) {
var dslCache = {};
var runner = self.createSpecRunner_($root);
angular.foreach(angular.scenario.dsl, function(fn, key) {
angular.forEach(angular.scenario.dsl, function(fn, key) {
dslCache[key] = fn.call($root);
});
angular.foreach(angular.scenario.dsl, function(fn, key) {
angular.forEach(angular.scenario.dsl, function(fn, key) {
self.$window[key] = function() {
var line = callerFile(3);
var scope = angular.scope(runner);
// Make the dsl accessible on the current chain
scope.dsl = {};
angular.foreach(dslCache, function(fn, key) {
angular.forEach(dslCache, function(fn, key) {
scope.dsl[key] = function() {
return dslCache[key].apply(scope, arguments);
};
+4 -4
View File
@@ -38,7 +38,7 @@ angular.scenario.dsl = angular.scenario.dsl || function(name, fn) {
return result;
var self = this;
var chain = angular.extend({}, result);
angular.foreach(chain, function(value, name) {
angular.forEach(chain, function(value, name) {
if (angular.isFunction(value)) {
chain[name] = function() {
return executeStatement.call(self, value, arguments);
@@ -101,7 +101,7 @@ function angularScenarioInit($scenario, config) {
output = config.scenario_output.split(',');
}
angular.foreach(angular.scenario.output, function(fn, name) {
angular.forEach(angular.scenario.output, function(fn, name) {
if (!output.length || indexOf(output,name) != -1) {
var context = body.append('<div></div>').find('div:last');
context.attr('id', name);
@@ -244,7 +244,7 @@ function browserTrigger(element, type) {
'select-multiple': 'change'
}[element.type] || 'click';
}
if (lowercase(nodeName(element)) == 'option') {
if (lowercase(nodeName_(element)) == 'option') {
element.parentNode.value = element.value;
element = element.parentNode;
type = 'change';
@@ -285,7 +285,7 @@ function browserTrigger(element, type) {
(function(fn){
var parentTrigger = fn.trigger;
fn.trigger = function(type) {
if (/(click|change|keyup)/.test(type)) {
if (/(click|change|keydown)/.test(type)) {
return this.each(function(index, node) {
browserTrigger(node, type);
});
+1 -1
View File
@@ -112,7 +112,7 @@ angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior,
var args = Array.prototype.slice.call(arguments, 1);
selector = (self.selector || '') + ' ' + (selector || '');
selector = _jQuery.trim(selector) || '*';
angular.foreach(args, function(value, index) {
angular.forEach(args, function(value, index) {
selector = selector.replace('$' + (index + 1), value);
});
var result = $document.find(selector);
+1 -1
View File
@@ -21,5 +21,5 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
(function(window, document, previousOnLoad){
(function(window, document){
var _jQuery = window.jQuery.noConflict(true);

Some files were not shown because too many files have changed in this diff Show More