Compare commits

...

203 Commits

Author SHA1 Message Date
Igor Minar 9f947e5b8b cutting the 0.9.10 flea-whisperer release 2011-01-26 23:51:06 -08:00
Igor Minar 75de7395bb validator doc example should not be escaped 2011-01-26 23:44:21 -08:00
Igor Minar 9f73b1f290 updating release notes for the 0.9.10 release 2011-01-26 23:36:41 -08:00
Misko Hevery bd33f60276 Added part of guide documentation and supporting changes to doc generator 2011-01-26 23:31:15 -08:00
Igor Minar 8682befc72 fix for stupid IE handling of named functions in mocks 2011-01-26 22:30:09 -08:00
Igor Minar 3f0a37f380 add missing test/mocks.js 2011-01-26 20:12:23 -08:00
Igor Minar f5d08963b0 split mocks and create $log and $exceptionHandler mocks
- split mocks between angular-mocks.js and mocks.js
- src/angular-mocks.js now contains only mocks that we want to ship
- test/mocks.js contains mocks that we use internally for testing
  angular
- created angular.mock namespace
- created public $exceptionHandler mock rethrows errors
- created public $log mock stores all logs messages in an array that can
  be accessed to make assertions
- internally we now have factory to create $exceptionHandler
  that we can assert on
- internally we also keep track of all messages logged and
  fail tests if messages were not expected and cleaned up (checked
  via global beforeEach and afterEach)
- updated RakeFile and docs reader.js to point to the new
  angular-mocks.js location
- made real $exceptionHandler and $log factories accessible from tests
  and simplified their specs
- fixed typos in several spec descriptions
- added log assertions throughout the test suite
2011-01-26 15:46:05 -08:00
Igor Minar 7a48ee6aa9 fix deprication typo in the doc generator 2011-01-26 15:37:22 -08:00
Igor Minar 6512a736ac adding jsdoc for angular namespace 2011-01-26 15:37:22 -08:00
Igor Minar 9e0fa5b7c8 compile but don't bind option elements nested in a nameless select
otherwise an exception is thrown unexpectidly
2011-01-25 20:49:25 -08:00
Igor Minar 9368ea3814 Scope should retrieve $log and $exceptionHandler via $service
- fix $log and $exceptionHandler retrieval
- remove reference to non-existent `error` handler
- update tests
2011-01-25 20:49:25 -08:00
Misko Hevery a6a4c18ecd added any edit perf to make sure no trailing whitespace 2011-01-24 14:23:51 -08:00
Misko Hevery d8acfe7389 add @this and @exampleDescription tags
(also removed markdownNoP, now done through CSS)
2011-01-24 14:23:51 -08:00
Misko Hevery b036c0bc58 enable docs tests 2011-01-24 14:23:51 -08:00
Misko Hevery bf03eb007c Added SiteMap generation to the documentation 2011-01-24 14:23:51 -08:00
Misko Hevery c2f2587a79 fixed example rendering, add tests for it. 2011-01-24 14:23:51 -08:00
Misko Hevery 5d0d34ae72 remove trailing whitespace 2011-01-24 14:23:51 -08:00
Misko Hevery 70c74a9c4e add @this and @exampleDescription tags
(also removed markdownNoP, now done through CSS)
2011-01-24 14:23:51 -08:00
Misko Hevery 22c5b7059b updated doc styling for methods/properties 2011-01-24 14:23:50 -08:00
Misko Hevery 190498efd7 remove jasmine stack traces from the test output 2011-01-24 14:23:50 -08:00
Igor Minar a5990050d4 ng:view should propagate evals to the current child scope
- this change is needed because of previously reverted $route changes
  that used to propagate evals automatically.
- also added docs to highlight how the eval propagation works
2011-01-24 14:03:43 -08:00
Igor Minar 8d507df8c9 Revert "$route should create child scope via $new"
This reverts commit a5eb3ed107.

See 9ca2facb for reasoning.
2011-01-24 14:03:42 -08:00
Igor Minar 567f33823b Revert "fix ng:include issue introduced by a5eb3ed1"
This reverts commit 17ee0f031a.

this and a5eb3ed1 introduced scope leakage that can't be resolved
until we fix up scope relationships and eval propagation.
I'm reverting both this and a5eb3ed1 until we can properly address
the issue.
2011-01-24 14:03:42 -08:00
Igor Minar 5340d1e0b1 fix for infinite loop in retrieveScope with jQuery + specs
- retrieveScope run into infinite loop if called on DOM tree
  that doesn't contain scope reference (happens only with jQuery)
- added missing specs for retrieveScope function
2011-01-24 14:03:42 -08:00
Igor Minar 8d91ec4173 improve angular.Scope.$eval docs 2011-01-24 14:03:42 -08:00
Igor Minar 746e7a9768 update example/temp.html template 2011-01-24 14:03:42 -08:00
Igor Minar 94737cd017 $cookies service should not call $eval during $eval
- added comment
- removed $eval call
- changed the code to not require $eval
- updated specs
2011-01-24 14:03:41 -08:00
Igor Minar c8bb044be1 fix clobber test in angular-bootstrap.js for FF 2011-01-24 14:03:17 -08:00
Igor Minar 4a569560d8 null and other falsy values should not be rendered in the view
Closes #242
2011-01-20 12:55:03 -08:00
Igor Minar 17ee0f031a fix ng:include issue introduced by a5eb3ed1 2011-01-19 21:47:14 -08:00
Igor Minar 10646c9f6f add ng:view widget 2011-01-19 15:53:59 -08:00
Igor Minar 964e394555 fix argument variables in compiler 2011-01-19 15:53:59 -08:00
Igor Minar 2c0f7ffe3a ngdoc should escape content of pre in markdown 2011-01-19 15:53:58 -08:00
Igor Minar a5eb3ed107 $route should create child scope via $new 2011-01-19 15:52:27 -08:00
Igor Minar 63690d1892 angular internals should not call methods via angular namespace 2011-01-19 15:52:27 -08:00
Misko Hevery 050e5d773f verify that issue# 162 was fixed with doc rewrite 2011-01-18 15:32:46 -08:00
Igor Minar da4b8a74c3 fix spec title for angular.formatter.index 2011-01-18 15:15:33 -08:00
Misko Hevery 5be92f7b9c fix broken build for IE. Read option.type==select-multiple rather then check presence of multiple attribute 2011-01-18 15:02:51 -08:00
Misko Hevery 459a01e582 improved dump function to atomatically render HTML 2011-01-18 15:02:51 -08:00
Igor Minar 72e554ffeb doc widgets examples should display angular src as CAO instead of DAO 2011-01-18 14:42:23 -08:00
Igor Minar 3bd96609e9 remove script.type attribute from example widgets since its optional in html5 2011-01-18 14:42:23 -08:00
Igor Minar d73635d573 @returns type in docs should be wrapped in curlies 2011-01-18 14:42:22 -08:00
Igor Minar ad3b811b8b fixes, examples and tests for angular.Object.* docs 2011-01-18 14:42:18 -08:00
Igor Minar d3f7bd699e various doc fixes for angular.Array.* apis + example and spec limitTo 2011-01-18 11:15:36 -08:00
Igor Minar 1c55123f9c rewrite of several major portions of angular.service docs 2011-01-17 22:16:15 -08:00
Misko Hevery 1d7b9d5626 support compilation in HTML widgets 2011-01-14 10:30:05 -08:00
Misko Hevery 347be5ae9a fixed select with ng:format
select (one/multiple) could not chose from a list of objects, since DOM requires string ids.
Solved by adding index formatter, which exposed incorrect handling of formatters in select
widgets.
2011-01-14 10:30:00 -08:00
Igor Minar 934f44f69e preparing the 0.9.10 flea-whisperer iteration 2011-01-14 10:22:10 -08:00
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
154 changed files with 10750 additions and 4073 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,"/>
+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
@@ -0,0 +1,16 @@
#Mon Jan 24 10:31:47 PST 2011
activeContentFilterList=*.makefile,makefile,*.Makefile,Makefile,Makefile.*,*.mk,MANIFEST.MF
addNewLine=true
convertActionOnSaave=AnyEdit.CnvrtTabToSpaces
eclipse.preferences.version=1
inActiveContentFilterList=
javaTabWidthForJava=true
org.eclipse.jdt.ui.editor.tab.width=2
projectPropsEnabled=false
removeTrailingSpaces=true
replaceAllSpaces=false
replaceAllTabs=false
saveAndAddLine=true
saveAndConvert=true
saveAndTrim=true
useModulo4Tabs=false
+180
View File
@@ -1,3 +1,183 @@
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
### Features
- new [`ng:view`](http://docs.angularjs.org/#!angular.widget.ng:view) widget to simplify integration
with the `$route` service
- the content of all standard HTML widgets is now being processed
(e.g. `<button>{{foo}}</button>` works now) (commit 1d7b9d56)
- new [`$log`](http://docs.angularjs.org/#!angular.mock.service.$log) and
[`$exceptionHandler`](http://docs.angularjs.org/#!angular.mock.service.$exceptionHandler) service
mocks now part of `angular-mocks.js` (commit f5d08963)
### Bug Fixes
- <select> (one/multiple) could not chose from a list of objects (commit 347be5ae)
- null and other falsy values should not be rendered in the view (issue #242)
### Docs
- rewrite of several major portions of angular.service.*, angular.Array.*, angular.Object.* docs
- added support for [sitemap]((http://docs.angularjs.org/sitemap.xml) to make the docs indexable by
search crawlers
- transition of Developer Guide docs from the wiki into docs.angularjs.org
- lots of improvements related to formatting of the content of docs.anguarjs.org
# <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
+2 -2
View File
@@ -158,7 +158,7 @@ end
desc 'Generate docs'
task :docs do
`node docs/collect.js`
`node docs/src/gen-docs.js`
end
@@ -174,7 +174,7 @@ task :package => [:clean, :compile, :docs] do
FileUtils.rm_r(path_to('pkg'), :force => true)
FileUtils.mkdir_p(pkg_dir)
['test/angular-mocks.js',
['src/angular-mocks.js',
path_to('angular.js'),
path_to('angular.min.js'),
path_to('angular-ie-compat.js'),
+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.
+166
View File
@@ -0,0 +1,166 @@
@workInProgress
@ngdoc overview
@name angular.service
@description
# Overview
Services are substituable objects, which are wired together using dependency injection (DI).
Each service could have dependencies (other services), which are passed in constructor.
Because JS is dynamicaly typed language, dependency injection can not use static types
to identify these dependencies, so each service must explicitely define its dependencies.
This is done by `$inject` property.
# Built-in services
angular provides a set of services for common operations. These services can be overriden by custom
services if needed.
Like other core angular variables and identifiers, the built-in services always start with `$`.
* `{@link angular.service.$browser $browser}`
* `{@link angular.service.$window $window}`
* `{@link angular.service.$document $document}`
* `{@link angular.service.$location $location}`
* `{@link angular.service.$log $log}`
* `{@link angular.service.$exceptionHandler $exceptionHandler}`
* `{@link angular.service.$hover $hover}`
* `{@link angular.service.$invalidWidgets $invalidWidgets}`
* `{@link angular.service.$route $route}`
* `{@link angular.service.$xhr $xhr}`
* `{@link angular.service.$xhr.error $xhr.error}`
* `{@link angular.service.$xhr.bulk $xhr.bulk}`
* `{@link angular.service.$xhr.cache $xhr.cache}`
* `{@link angular.service.$resource $resource}`
* `{@link angular.service.$cookies $cookies}`
* `{@link angular.service.$cookieStore $cookieStore}`
# Writing your own custom services
angular provides only set of basic services, so for any nontrivial application it will be necessary
to write one or more custom services. To do so, a factory function that creates a services needs to
be registered with angular's dependency injector. This factory function must return an object - the
service (it is not called with the `new` operator).
**angular.service** accepts three parameters:
- `{string} name` - Name of the service.
- `{function()} factory` - Factory function (called just once by DI).
- `{Object} config` - Configuration object with following properties:
- `$inject` - {Array.<string>} - Array of service ids that this service depends on. These
services will be passed as arguments into the factory function in the same order as specified
in the `$inject` array. Defaults to `[]`.
- `$eager` - {boolean} - If true, the service factory will be called and thus, the service will
be instantiated when angular boots. If false, service will be lazily instantiated when it is
first requested during instantiation of a dependant. Defaults to `false`.
The `this` of the factory function is bound to the root scope of the angular application.
angular enables services to participate in dependency injection (DI) by registering themselves with
angular's DI system (injector) under a `name` (id) as well as by declaring dependencies which need
to be provided for the factory function of the registered service. The ability to swap dependencies
for mocks/stubs/dummies in tests allows for services to be highly testable.
Here is an example of very simple service. This service requires $window service (it's
passed as a parameter to factory function) and it's just a function.
This service simple stores all notifications and after third one, it displays all of them by
window alert.
<pre>
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
</pre>
And here is a unit test for this service. We use Jasmine spy (mock) instead of real browser's alert.
<pre>
var mock, notify;
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
notify = angular.service('notify')(mock);
});
it('should not alert first two notifications', function() {
notify('one');
notify('two');
expect(mock.alert).not.toHaveBeenCalled();
});
it('should alert all after third notification', function() {
notify('one');
notify('two');
notify('three');
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
});
it('should clear messages after alert', function() {
notify('one');
notify('two');
notify('third');
notify('more');
notify('two');
notify('third');
expect(mock.alert.callCount).toEqual(2);
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
});
</pre>
# Injecting services into controllers
Using services as dependencies for controllers is very similar to using them as dependencies for
another service.
JavaScript is dynamic language, so DI is not able to figure out which services to inject by
static types (like in static typed languages). Therefore you must specify the service name
by the `$inject` property - it's an array that contains strings with names of services to be
injected. The name must match the id that service has been registered as with angular.
The order of the services in the array matters, because this order will be used when calling
the factory function with injected parameters. The names of parameters in factory function
don't matter, but by convention they match the service ids.
<pre>
function myController($loc, $log) {
this.firstMethod = function() {
// use $location service
$loc.setHash();
};
this.secondMethod = function() {
// use $log service
$log.info('...');
};
}
// which services to inject ?
myController.$inject = ['$location', '$log'];
</pre>
@example
<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: <input type="text" name="number" ng:validate="integer" value="123">
</doc:source>
<doc:scenario>
it('should validate the default number string', function() {
expect(element('input[name=number]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate "foo"', function() {
input('number').enter('foo');
expect(element('input[name=number]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
# Writing your own Validators
Writing your own validator is easy. To make a function available as a
validator, just define the JavaScript function on the `angular.validator`
object. <angular/> passes in the input to validate as the first argument
to your function. Any additional validator arguments are passed in as
additional arguments to your function.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
the DOM in addition to transforming the input.
In this example we have written a upsTrackingNo validator.
It marks the input text "valid" only when the user enters a well-formed
UPS tracking number.
@css ng-validation-error
When validation fails, this css class is applied to the binding, making its borders red by
default.
@example
<script>
angular.validator('upsTrackingNo', function(input, format) {
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
return input.match(regexp)?"":"The format must match " + format;
});
</script>
<input type="text" name="trackNo" size="40"
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
value="1Z 123 456 78 9012 345 6"/>
@scenario
it('should validate correct UPS tracking number', function() {
expect(element('input[name=trackNo]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate in correct UPS tracking number', function() {
input('trackNo').enter('foo');
expect(element('input[name=trackNo]').attr('class')).
toMatch(/ng-validation-error/);
});
+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}}
+97
View File
@@ -0,0 +1,97 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Bootstrap
@description
# Bootstrap
This section explains how to bootstrap your application to the angular environment using either
the `angular.js` or `angular.min.js` script.
## The bootstrap code
Note that there are two versions of the bootstrap code that you can use:
* `angular-0.0.0.js` - this file is unobfuscated, uncompressed, and thus human-readable.
* `angular-0.0.0.min.js` - this is a compressed and obfuscated version of angular-debug.js.
In this section and throughout the Developer Guide, feel free to use `angular.min.js` instead of
`angular.js` when working through code examples.
## ng:autobind
The simplest way to get an angular application up and running is by inserting a script tag in your
HTML file that bootstraps the `angular.js` code and uses the special `ng:autobind` attribute,
like in this snippet of HTML:
<doc:example>
<doc:source>
Hello {{'World'}}!
</doc:source>
</doc:example>
The `ng:autobind` attribute tells angular to compile and manage the whole HTML document. The
compilation occurs in the page's onLoad handler. Note that you don't need to explicitly add an
onLoad event; auto bind mode takes care of all the magic for you.
## Manual bind
Using autobind mode is a handy way to start using angular, but advanced users who want more
control over the initialization process might prefer to use manual bind mode instead.
The best way to get started with manual bind mode is to look at the magic behind `ng:autobind`
by writing out each step of the autobind process explicitly. Note that the following code is
equivalent to the code in the previous section.
<pre>
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org">
<script type="text/javascript" src="http://code.angularjs.org/angular-0.0.0.min.js"></script>
<script type="text/javascript">
(function(window, previousOnLoad){
window.onload = function(){
try { (previousOnLoad||angular.noop)(); } catch(e) {}
angular.compile(window.document).$init();
};
})(window, window.onload);
</script>
<body>
Hello {{'World'}}!
</body>
</html>
</pre>
This is the sequence that your code should follow if you're writing your own manual binding code:
* After the page is loaded, find the root of the HTML template, which is typically the root of
the document.
* Run the HTML compiler, which converts the templates into an executable, bi-directionally
bound application.
# XML Namespace
**IMPORTANT:** When using angular you must declare the `ng` namespace using the `xmlns` tag.
If you don't declare the namespace, Internet Explorer does not render widgets properly.
<pre>
<html xmlns:ng="http://angularjs.org">
</pre>
# Create your own namespace
If you want to define your own widgets, you must create your own namespace and use that namespace
to form the fully qualified widget name. For example, you could map the alias my to your domain
and create a widget called my:widget. To create your own namespace, simply add another xmlsn tag
to your page, create an alias, and set it to your unique domain:
<pre>
<html xmlns:my="http://mydomain.com">
</pre>
# Global Object
The angular script creates a single global variable `angular` in the global namespace. All APIs are
bound to fields of this global object.
+163
View File
@@ -0,0 +1,163 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Compiler
@description
#Compiler
While angular might look like just a cool way to build web applications, the core of angular is
actually an HTML compiler. The default HTML transformations that this compiler provides are useful
for building generic apps, but you can also use them to create a domain-specific language for
building specific types of web applications.
The compiler allows you to add behavior to existing HTML through widgets, directives, and text
markup.
All of this compilation happens in the web browser, meaning no server is involved.
# The compilation process
This section describes the steps that angular's HTML compiler goes through. If you use
`ng:autobind` in your application, this compilation process happens automatically when the
application is initialized (e.g. when the user loads the app in a browser). If you're an advanced
user using manual bind mode, you can decide when and how often the compilation happens.
First, a bit of background of what the compilation step is for. Every type of
{@link angular.widget widget}, {@link angular.markup markup}, and
{@link angular.directive directive} in angular is defined with a compile function, and that
compile function returns an optional link function. Here is the relationship between the two:
* **compile function** - registers a listener for the widget, markup, or directive's expression.
This function is called exactly once.
* **link function** - sets up the listener. This function can be called multiple times, once per
cloned DOM element (e.g. repeating element).
Note that angular's built-in widgets, markup, and directives have predefined compile and link
functions that you don't need to modify. However, if you're writing your own widgets, markup, or
directives, you write compile and link functions. Refer to the Compiler API for more information.
When the HTML compiler compiles a page, it goes through 3 phases: Compile, Create Root Scope, and
Link.
## 1. Compile Phase
* Recursively traverse the DOM, depth-first.
* Look for a matching compile function of type widget, then markup, then directive.
* If a compile function is found then execute it.
* When the compile function completes, it should return a link function. Aggregate this link
function with all link functions returned previously by step 1c.
* Repeat steps 1c and 1d for all compile functions found. The result of the compilation step is
the aggregate link function, which comprises all of the individual link functions.
## 2. Create Root Scope
* Inject all of the services into the root scope.
## 3. Link Phase
* Execute the aggregate link function with the root scope. The aggregate link function calls all
the individual link functions that were generated in the compile phase.
* If there are any clones of the DOM caused by repeating elements, call the link function multiple
times, one for each repeating item.
Note that while the compile function is executed exactly once, the link function can be executed
multiple times: once for each iteration in a repeater.
# Example
The compilation process is best understood through example. Let's say that in your namespace my,
you want to create a new DOM element <my:greeter/>, which should display a greeting.
If we want this HTML source:
<pre>
<div ng:init="salutation='Hello'; name='World'">
<my:greeter salutation="salutation" name="name"/>
</div>
</pre>
To produce this DOM:
<pre>
<div ng:init="salutation='Hello'; name='World'">
<my:greeter salutation="salutation" name="name"/>
<span class="salutation">Hello</span>
<span class="name">World</span>!
</my:greeter>
</div>
</pre>
Write this widget definition (assuming you've already declared the my namespace in the page):
<pre>
angular.widget('my:greeter', function(compileElement){
var compiler = this;
compileElement.css('display', 'block');
var salutationExp = compileElement.attr('salutation');
var nameExp = compileElement.attr('name');
return function(linkElement){
var salutationSpan = angular.element('<span class="salutation"></span');
var nameSpan = angular.element('<span class="name"></span>');
linkElement.append(salutationSpan);
linkElement.append(compiler.text(' '));
linkElement.append(nameSpan);
linkElement.append(compiler.text('!'));
this.$watch(salutationExp, function(value){
salutationSpan.text(value);
});
this.$watch(nameExp, function(value){
nameSpan.text(value);
});
};
});
</pre>
Note: For more about widgets, see {@link angular.widget Widget}.
## Compilation process for this example
Here are the steps that the compiler goes through for the page that contains this widget definition:
### Compile Phase
* Recursively traverse the DOM depth-first.
* Find the angular.widget definition.
* Find and execute the widget's compileElement function, which includes the following steps:
* Add a style element with attribute display: block; to the template DOM so that the browser
knows to treat the element as block element for rendering. (Note: because this style element
was added on the template compileElement, this style is automatically applied to any clones
of the template (i.e. any repeating elements)).
* Extract the salutation and name HTML attributes as angular expressions.
* Return the aggregate link function, which includes just one link function in this example.
### Link Phase
* Execute the aggregate link function, which includes the following steps:
* Create a <span> element set to the salutation class
* Create a <span> element set to the name class.
* Add the span elements to the linkElement. (Note: be careful not to add them to the
compileElement, because that's the template.)
* Set up watches on the expressions. When an expression changes, copy the data to the
corresponding spans.
## Compiler API
If you define your own widgets, markup, or directives, you need to access the compiler API.
This section describes the methods on the compiler that you can call.
Note: As of 12 August 2010, these methods are subject to change.
Recall that the compile function's this is a reference to the compiler.
* `compile(element)` - returns `linker` - Invoke new instance of compiler to compile a DOM element
and return a linker function. You can apply the linker function to the original element or a
clone of the original element. The linker function returns a scope.
* `comment(commentText)` - returns `element` - Create a comment element.
* `element(elementName)` - returns `element` - Create an element by name.
* `text(text)` - returns `element` - Create a text element.
* `descend([set])` - returns `descend` - State Get or set the current descend state. If true the
compiler will descend to children elements.
* `directives([set])` - returns `directive` - State Get or set the current directives processing
state. The compiler will process directives only when directives set to true.
+41
View File
@@ -0,0 +1,41 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Data Binding
@description
# Data Binding
Data-binding allows you to treat the model as the single-source-of-truth of your application, and
consider the view as only a projection of the model, at all times. The process of copying the model
values to the view, and any changes to the view by the user to the model, is known as data-binding.
## Classical Template Systems
<img class="right" src="img/One_Way_Data_Binding.png"/>
At the highest level, angular looks like a just another templating system. But there is one
important reason why angular templating system is different and makes it very good fit for
application development: two-way data binding.
Most templating systems bind data in only one direction: they merge a template and model together
into a view, as illustrated in the diagram to the right. After the merge occurs, any changes to
the model or in related sections of the view are NOT automatically reflected in the view. Worse,
any changes that the user makes to the view are not reflected in the model. This means that the
developer has to write code that constantly syncs the view with the model and the model with the
view.
# angular Template Systems
<img class="right" src="img/Two_Way_Data_Binding.png"/>
The way angular templates works is different, as illustrated in the diagram on the right. They are
different because first the template (which is the uncompiled HTML along with any additional markup
or directives) is compiled on the browser, and second, the compilation step produces a live view.
We say live because any changes to the view are immediately reflected in the model, and any changes
in the model are propagated to the view. This makes the model always the single-source-of-truth for
the application state, greatly simplifying the programing model for the developer. You can think of
the view as simply an instant projection of your model.
Because the view is just a projection of the model, the controller is completely separated from the
view and unaware of it. This makes testing a snap because it is easy to test your controller in
isolation without the view and the related DOM/browser dependency.
For details about how data binding works in angular, see {@link angular.scope Scope}.
+36
View File
@@ -0,0 +1,36 @@
@workInProgress
@ngdoc overview
@name Developer Guide
@description
* {@link guide.overview Overview} - An overview of angular, including its philosophy and how it
works.
* {@link guide.bootstrap Bootstrap} - How to bootstrap your application to the angular environment.
* {@link guide.template Template} - How to define your application's view using HTML, CSS, and
other built-in angular constructs.
* {@link guide.compiler Compiler} - All about the HTML compiler that's at the core of angular.
* {@link angular.directive Directive} - How to use XML attributes to augment an existing DOM
element.
* {@link angular.markup Markup} - How to use markup to create shorthand for a widget or a
directive. For example, markup is what allows you to use the double curly brace notation
`{{}}` to bind expressions to elements.
* {@link guide.data-binding Data Binding} - About the mechanism that keeps the model the single
source of truth of your application at all times, with the view as a live projection of the
model.
* {@link angular.filter Filter} - How to format your data for display to the user.
* {@link angular.widget Widget} - How to create new DOM elements that the browser doesn't already
understand.
* {@link angular.validator Validator} - How to validate user input.
* {@link angular.formatter Formatter} - How to format stored data to user-readable text and
parse the text back to the stored form.
* {@link guide.css CSS} - Built-in CSS classes, when angular assigns them, and how to override
their styles.
* {@link angular.scope Scope} - The model in the model-view-controller design pattern. You can
think about scopes as the JavaScript objects that have extra APIs for registering watchers.
* {@link guide.expression Expression} - The bindings that are embedded in an angular View.
* {@link angular.service Service} - Objects that are wired through dependency injection and then
injected into the root scope.
* {@link guide.testing Testing}
* service:$browser(mock)
* {@link guide.downloading Downloading} - How to download, compile, and host the angular
environment on your own server.
+166
View File
@@ -0,0 +1,166 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Overview
@description
# What is angular?
Angular teaches your old browser new tricks. It is what HTML would have been if it had been
designed for building web applications.
Take a simple example of user input as shown below. If you were using just HTML and JavaScript to
implement this form, you would need to define listeners, DOM updates, and complex input validators
in order to update and format the result. In angular you can achieve the effect with zero lines
of JavaScript code using a declarative approach. Click on the source tab of the example below to
view the angular implementation of this form.
<doc:example>
<doc:source>
QTY: <input name="qty" value="1" ng:validate="integer:0" ng:required/>
*
Cost: <input name="cost" value="19.95" ng:validate="number" ng:required/>
=
{{qty * cost | currency}}
</doc:source>
<doc:scenario>
it('should show of angular binding', function(){
expect(binding('qty * cost')).toEqual('$19.95');
input('qty').enter('2');
input('cost').enter('5.00');
expect(binding('qty * cost')).toEqual('$10.00');
});
</doc:scenario>
</doc:example>
Angular is to AJAX apps as Ruby on Rails is to round trip apps.
# Angular frees you from:
* **Registering callbacks:** Registering callbacks clutters your code, and it makes it hard to see
the forest from the trees. Removing common boilerplate code such as callbacks is advantageous
because it leaves the JavaScript with a more succinct version of your code, better describing
what your application does.
* **Manipulating HTML DOM programatically:** Manipulating HTML DOM is a cornerstone of AJAX
applications, but it is very cumbersome and error-prone. By declaratively describing how the UI
should change as your application state changes, you are freed from low level DOM manipulation
activities. Most applications written with angular never have to programatically manipulate
the DOM.
* **Marshaling data to and from the UI:** CRUD operations make up the majority of most AJAX
applications. The flow of marshaling data from the server to an internal object to a HTML form,
validating the form, displaying validation errors, returning to an internal model and then back
to the server creates a lot of boilerplate code. angular eliminates almost all of this
boilerplate. leaving code that is richer and describes the overall flow of the application
rather than implementation details.
* **Writing tons of initialization code just to get started:** Typically you need to write a lot
of plumbing and initialization code just to get a basic "Hello World" AJAX app working. With
angular you can bootstrap your app easily using services, which are auto-injected into your
application in a GUICE-like dependency-injection style. This allows you to get started
developing features quickly. As a bonus, you get full control over the initialization process
in automated tests.
# angular is/has:
* **An HTML Compiler:** angular is an HTML compiler in the browser. It allows you to give meaning
to any HTML element, attribute, or text and create new primitives as building blocks for your
application.
* **Declarative:** Declarative means that you describe what the page looks like rather than
instructing how to draw the page. HTML is great at declaring static documents. Angular extends
the declarative nature of HTML beyond static documents to define dynamic applications.
* **Declarative Templates:** In angular, you write HTML to declare the view and UI templates for
your application. You can express many common application constructs without using any
JavaScript at all.
* **Bidirectional Data Binding:** Allows your application to have a single source of truth
(your model), where the HTML is a projection of the internal state of your application, which
you can provide to the user in a declarative way.
* **Built-in Services:** angular provides many standard AJAX operations to get you going quickly,
and dependency-injection allows you to swap them out as needed. Common services include:
Dependency Inject, History Management, URL Router, AJAX/XHR requests, and data caching,
to name a few.
* **Very testable:** Testing difficulty is dramatically affected by the way you structure your
code. With angular, testability is baked in.
# angular is NOT a:
* **Library:** You don't call its functions.
* **Framework:** It does not call your functions.
* **DOM Manipulation Library:** It does not provide a way to manipulate DOM, but does provide
primitives to create UI projections of your data.
* **Widget Library:** There are lots of existing widget libraries that you can integrate with
angular.
# Not just another templating system
At the highest level, angular looks like a just another templating system, but there are few
important reasons why angular is different and makes it a very good fit for application
development.
Angular:
* **Uses HTML/CSS syntax:** This makes it easy to read and can be edited with existing HTML/CSS
authoring tools.
* **Extends HTML vocabulary:** Angular allows you to create new HTML tags, which expand into
dynamic UI components.
* **Executes in the browser:** Removes the round trip to the server for many operations and
creates instant feedback for users.
* **Bidirectional data binding:** The model is the single source of truth. Programmatic changes
to the model are automatically reflected in the view. Any changes by the user to the view are
automatically reflected in the model.
* **Services:** These allow for a reusable way of injecting dependencies into an application.
* **MVC:** Clean separation between model-view-controller, which aids in understanding,
maintenance, and testing of large systems.
# The angular philosophy
Angular is built around the belief that declarative code is preferred over imperative when it
comes to building UIs and connecting the pieces together.
As an example, if you wanted to add a new label to your application, you could do so by simply
adding text to the HTML template, saving the code, and refreshing your browser:
<pre>
<span class="label">Hello</span>
</pre>
In programmatic systems you would have to write and run code like this:
<pre>
var label = new Label();
label.setText('Hello');
label.setClass('label');
parent.addChild(label);
</pre>
## Benefits:
* Compile-free: Change your template and logic code and reload the browser to see it run
immediately. In contrast, programmatic serverside views often need to be compiled and executed
to be viewed, which takes time. This dramatically increases the speed of your development cycle.
* Declarative templates are easier to understand and change than programmatic instructions.
* Declarative templates can be edited in existing HTML editors such as DreamWeaver, Eclipse,
TextMate, Vim, etc.
* Declarative templates can be edited by web designers without the need to work with web
developers.
HTML is missing certain features, which angular adds via its compiler, thereby "teaching" the
browser these new tricks:
* Dynamic behavior
* Componentizing HTML snippets into reusable components
* Dynamically include other HTML templates
* Two-way data binding
* Rich validation in forms
* Model-View-Controller modularization
# Watch a presentation about angular
<object width="480" height="385">
<param name="movie" value="http://www.youtube.com/v/elvcgVSynRg&amp;hl=en_US&amp;fs=1"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
<embed src="http://www.youtube.com/v/elvcgVSynRg&amp;hl=en_US&amp;fs=1"
type="application/x-shockwave-flash" allowscriptaccess="always"
allowfullscreen="true" width="480" height="385"></embed>
</object>
{@link https://docs.google.com/present/edit?id=0Abz6S2TvsDWSZDQ0OWdjaF8yNTRnODczazdmZg&hl=en&authkey=CO-b7oID Presentation}
|
{@link https://docs.google.com/document/edit?id=1ZHVhqC0apbzPRQcgnb1Ye-bAUbNJ-IlFMyPBPCZ2cYU&hl=en&authkey=CInnwLYO Source}
+22
View File
@@ -0,0 +1,22 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Template
@description
#Template
You can think of a template in angular as a domain-specific language that you can use to easily
build the view of your web application. You create a template by writing HTML and CSS, and you can
add any constructs that you want to the HTML. This means that you can attach rendering and behavior
to any HTML element, attribute or markup text.
In addition to writing HTML and CSS, you can also use the following angular constructs to create
your template:
* **Directive** - XML attributes that augment an existing DOM element.
* **Markup** - Lets you create shorthand for a widget or a directive. For example, markup is what
allows you to use the double curly brace notation {{}} to bind expressions to
elements.
* **Filter** - Lets you format your data for display to the user.
* **Widget** - Lets you create new DOM elements that the browser doesn't already understand.
* **Validator** - Lets you validate user input.
* **Formatter** - Lets you format the input object into a user readable view.
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

-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;
}
+61
View File
@@ -0,0 +1,61 @@
var DOM = require('dom.js').DOM;
describe('dom', function(){
var dom;
beforeEach(function(){
dom = new DOM();
});
describe('example', function(){
it('should render code, live, test', function(){
dom.example('desc', 'src', 'scenario');
expect(dom.toString()).toEqual(
'<h1>Example</h1>\n' +
'<div class="example">' +
'desc<doc:example><doc:source>src</doc:source>\n' +
'<doc:scenario>scenario</doc:scenario>\n'+
'</doc:example>\n' +
'</div>\n');
});
it('should render non-live, test with description', function(){
dom.example('desc', 'src', false);
expect(dom.toString()).toEqual('<h1>Example</h1>\n' +
'<div class="example">' +
'desc<div ng:non-bindable="">' +
'<pre class="brush: js; html-script: true;">src</pre>\n' +
'</div>\n' +
'</div>\n');
});
it('should render non-live, test', function(){
dom.example('desc', 'src', false);
expect(dom.toString()).toContain('<pre class="brush: js; html-script: true;">src</pre>');
});
});
describe('h', function(){
it('should render using function', function(){
var cbThis;
var cdValue;
dom.h('heading', 'content', function(value){
cbThis = this;
cbValue = value;
});
expect(cbThis).toEqual(dom);
expect(cbValue).toEqual('content');
});
it('should update heading numbers', function(){
dom.h('heading', function(){
this.html('<h1>sub-heading</h1>');
});
expect(dom.toString()).toContain('<h1>heading</h1>');
expect(dom.toString()).toContain('<h2>sub-heading</h2>');
});
});
});
+454
View File
@@ -0,0 +1,454 @@
var ngdoc = require('ngdoc.js');
var DOM = require('dom.js').DOM;
describe('ngdoc', function(){
var Doc = ngdoc.Doc;
var dom;
beforeEach(function(){
dom = new DOM();
this.addMatchers({
toContain: function(text) {
this.actual = this.actual.toString();
return this.actual.indexOf(text) > -1;
}
});
});
describe('Doc', function(){
describe('metadata', function(){
it('should find keywords', function(){
expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world');
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
});
it('should have shortName', function(){
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng:c');
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
});
it('should have depth information', function(){
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].depth).toEqual(2);
expect(ngdoc.metadata([d2])[0].depth).toEqual(2);
expect(ngdoc.metadata([d3])[0].depth).toEqual(1);
});
});
describe('parse', function(){
it('should convert @names into properties', function(){
var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n');
doc.parse();
expect(doc.name).toEqual('name');
expect(doc.desc).toEqual('desc\ndesc2');
expect(doc.dep).toEqual('');
});
it('should parse parameters', function(){
var doc = new Doc(
'@param {*} a short\n' +
'@param {Type} b med\n' +
'@param {Class=} [c=2] long\nline');
doc.parse();
expect(doc.param).toEqual([
{name:'a', description:'<p>short</p>', type:'*', optional:false, 'default':undefined},
{name:'b', description:'<p>med</p>', type:'Type', optional:false, 'default':undefined},
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'}
]);
});
it('should parse return', function(){
var doc = new Doc('@returns {Type} text *bold*.');
doc.parse();
expect(doc.returns).toEqual({
type: 'Type',
description: '<p>text <em>bold</em>.</p>'
});
});
it('should not remove extra line breaks', function(){
var doc = new Doc('@example\nA\n\nB');
doc.parse();
expect(doc.example).toEqual('A\n\nB');
});
it('should parse filename', function(){
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
doc.parse(0);
expect(doc.id).toEqual('a.b');
expect(doc.name).toEqual('friendly name');
});
it('should escape <doc:source> element', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:source>\n<>\n</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<doc:source>\n&lt;&gt;\n</doc:source></doc:example><p>after</p>');
});
it('should escape <doc:scenario> element', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<doc:scenario>\n&lt;&gt;\n</doc:scenario></doc:example><p>after</p>');
});
describe('sorting', function(){
function property(name) {
return function(obj) {return obj[name];};
}
function noop(){}
function doc(type, name){
return {
id: name,
ngdoc: type,
keywords: noop
};
}
var angular_widget = doc('overview', 'angular.widget');
var angular_x = doc('function', 'angular.x');
var angular_y = doc('property', 'angular.y');
it('should put angular.fn() in front of angular.widget, etc', function(){
expect(ngdoc.metadata([angular_widget, angular_y, angular_x]).map(property('id')))
.toEqual(['angular.x', 'angular.y', 'angular.widget' ]);
});
});
});
});
describe('scenario', function(){
it('should render from @example/@scenario and <doc:example>', function(){
var doc = new Doc(
'@id id\n' +
'@description <doc:example><doc:scenario>scenario0</doc:scenario></doc:example>' +
'@example exempleText\n' +
'@scenario scenario1\n' +
'@scenario scenario2').parse();
expect(ngdoc.scenarios([doc])).toContain('describe("id"');
expect(ngdoc.scenarios([doc])).toContain('navigateTo("index.html#!id")');
expect(ngdoc.scenarios([doc])).toContain('\n scenario0\n');
expect(ngdoc.scenarios([doc])).toContain('\n scenario1\n');
expect(ngdoc.scenarios([doc])).toContain('\n scenario2\n');
});
});
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>, but escape the html escape the content', function(){
expect(markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
toEqual(
'<p>bah x</p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
'&lt;b&gt;angular&lt;/b&gt;.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><h1>One</h1><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 : '<p>Number \n to format.</p>' }]);
});
it('should parse with default and optional', function(){
var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc');
doc.parse();
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'fractionSize',
optional: true,
'default' : '2',
description : '<p>desc</p>' }]);
});
});
describe('@requires', function() {
it('should parse more @requires tag into array', function() {
var doc = new Doc('@requires $service\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: '<p>descrip tion</p>'});
});
it('should transform description of @returns with markdown', function() {
var doc = new Doc("@returns {string} descrip *tion*");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip <em>tion</em></p>'});
});
it('should support multiline content', function() {
var doc = new Doc("@returns {string} description\n new line\n another line");
doc.parse();
expect(doc.returns).
toEqual({type: 'string', description: '<p>description\n new line\n another line</p>'});
});
});
describe('@description', function(){
it('should support pre blocks', function(){
var doc = new Doc("@description <pre>abc</pre>");
doc.parse();
expect(doc.description).
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>');
});
it('should support multiple pre blocks', function() {
var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
doc.parse();
expect(doc.description).
toBe('<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
'<h1>bah</h1>\n\n' +
'<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
});
it('should support nested @link annotations with or without description', function() {
var doc = new Doc("@description " +
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
'dad{@link angular.foo}\n\n' +
'{@link angular.directive.ng:foo ng:foo}');
doc.parse();
expect(doc.description).
toContain('foo <a href="#!angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
toContain('da <a href="#!angular.foo"><code>bar foo bar</code></a>');
expect(doc.description).
toContain('dad<a href="#!angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
toContain('<a href="#!angular.directive.ng:foo"><code>ng:foo</code></a>');
});
});
describe('@example', function(){
it('should not remove {{}}', function(){
var doc = new Doc('@example text {{ abc }}');
doc.parse();
expect(doc.example).toEqual('text {{ abc }}');
});
});
describe('@exampleDescription', function(){
it('should render example description', function(){
var doc = new Doc('@exampleDescription some\n text');
doc.ngdoc = "filter";
doc.parse();
expect(doc.html()).toContain('<p>some\n text');
});
it('should alias @exampleDescription to @exampleDesc', function(){
var doc = new Doc('@exampleDesc some\n text');
doc.ngdoc = "filter";
doc.parse();
expect(doc.html()).toContain('<p>some\n text');
});
it('should render description in related method', function(){
var doc = new Doc();
doc.ngdoc = 'service';
doc.methods = [new Doc('@ngdoc method\n@exampleDescription MDesc\n@example MExmp').parse()];
doc.properties = [new Doc('@ngdoc property\n@exampleDescription PDesc\n@example PExmp').parse()];
expect(doc.html()).toContain('<p>MDesc</p><div ng:non-bindable=""><pre class="brush: js; html-script: true;">MExmp</pre>');
expect(doc.html()).toContain('<p>PDesc</p><div ng:non-bindable=""><pre class="brush: js; html-script: true;">PExmp</pre>');
});
});
describe('@depricated', function() {
it('should parse @depricated', function() {
var doc = new Doc('@deprecated Replaced with foo.');
doc.parse();
expect(doc.deprecated).toBe('Replaced with foo.');
});
});
describe('@this', function(){
it('should render @this', function() {
var doc = new Doc('@this I am self.');
doc.ngdoc = 'filter';
doc.parse();
expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
'<div>' +
'<p>I am self.</p>' +
'</div>\n');
expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
'<div><p>I am self.</p></div>');
});
});
});
describe('usage', function(){
describe('overview', function(){
it('should supress description heading', function(){
var doc = new Doc('@ngdoc overview\n@name angular\n@description\n#heading\ntext');
doc.parse();
expect(doc.html()).toContain('text');
expect(doc.html()).toContain('<h2>heading</h2>');
expect(doc.html()).not.toContain('Description');
});
});
describe('filter', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'formatter',
shortName:'myFilter',
param: [
{name:'a'},
{name:'b'}
]
});
doc.html_usage_filter(dom);
expect(dom).toContain('myFilter_expression | myFilter:b');
expect(dom).toContain('angular.filter.myFilter(a, b)');
});
});
describe('validator', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'validator',
shortName:'myValidator',
param: [
{name:'a'},
{name:'b'}
]
});
doc.html_usage_validator(dom);
expect(dom).toContain('ng:validate="myValidator:b"');
expect(dom).toContain('angular.validator.myValidator(a, b)');
});
});
describe('formatter', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'formatter',
shortName:'myFormatter',
param: [
{name:'a'},
]
});
doc.html_usage_formatter(dom);
expect(dom).toContain('ng:format="myFormatter:a"');
expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);');
expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);');
});
});
});
});
+23
View File
@@ -0,0 +1,23 @@
var SiteMap = require('sitemap.js').SiteMap;
var Doc = require('ngdoc.js').Doc;
describe('sitemap', function(){
it('should render empty sitemap', function(){
var map = new SiteMap([]);
expect(map.render()).toEqual([
'<?xml version="1.0" encoding="UTF-8"?>',
'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
'</sitemapindex>', ''].join('\n'));
});
it('should render ngdoc url', function(){
var map = new SiteMap([new Doc({name: 'a.b.c<>\'"&'})]);
expect(map.render()).toContain([
' <url>',
'<loc>http://docs.angularjs.org/#!a.b.c&lt;&gt;&apos;&quot;&amp;</loc>',
'<changefreq>weekly</changefreq>',
'</url>'].join(''));
});
});
+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);
+31
View File
@@ -0,0 +1,31 @@
exports.SiteMap = SiteMap;
/**
* @see http://www.sitemaps.org/protocol.php
*
* @param docs
* @returns {SiteMap}
*/
function SiteMap(docs){
this.render = function(){
var map = [];
map.push('<?xml version="1.0" encoding="UTF-8"?>');
map.push('<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
docs.forEach(function(doc){
map.push(' <url><loc>http://docs.angularjs.org/#!' +
encode(doc.name) + '</loc><changefreq>weekly</changefreq></url>');
});
map.push('</sitemapindex>');
map.push('');
return map.join('\n');
};
function encode(text){
return text
.replace(/&/mg, '&amp;')
.replace(/</mg, '&lt;')
.replace(/>/mg, '&gt;')
.replace(/'/mg, '&apos;')
.replace(/"/mg, '&quot;');
}
}
+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');
+143
View File
@@ -0,0 +1,143 @@
/**
* DOM generation class
*/
exports.DOM = DOM;
exports.htmlEscape = htmlEscape;
//////////////////////////////////////////////////////////
function htmlEscape(text){
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function DOM(){
this.out = [];
this.headingDepth = 0;
}
var INLINE_TAGS = {
i: true,
b: true
};
DOM.prototype = {
toString: function() {
return this.out.join('');
},
text: function(content) {
if (typeof content == "string") {
this.out.push(htmlEscape(content));
} else if (typeof content == 'function') {
content.call(this, this);
} else if (content instanceof Array) {
this.ul(content);
}
},
html: function(html) {
if (html) {
var headingDepth = this.headingDepth;
for ( var i = 10; i > 0; --i) {
html = html
.replace(new RegExp('(<\/?h)' + i + '(>)', 'gm'), function(all, start, end){
return start + (i + headingDepth) + end;
});
}
this.out.push(html);
}
},
tag: function(name, attr, text) {
if (!text) {
text = attr;
attr = {};
if (name == 'code')
attr['ng:non-bindable'] = '';
}
this.out.push('<' + name);
for(var key in attr) {
this.out.push(" " + key + '="' + attr[key] + '"');
}
this.out.push('>');
this.text(text);
this.out.push('</' + name + '>');
if (!INLINE_TAGS[name])
this.out.push('\n');
},
code: function(text) {
this.tag('div', {'ng:non-bindable':''}, function(){
this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
});
},
example: function(description, source, scenario) {
if (description || source || scenario) {
this.h('Example', function(){
if (description)
this.html(description);
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.headingDepth++;
this.tag('h' + this.headingDepth, heading);
var className = typeof heading == 'string'
? {'class': heading.toLowerCase().replace(/[^\d\w_]/mg, '-').replace(/-+/gm, '-')}
: null;
if (content instanceof Array) {
this.ul(content, className, fn);
} else if (fn) {
this.tag('div', className, function(){
fn.call(this, content);
});
} else {
this.tag('div', className, content);
}
this.headingDepth--;
},
h1: function(attr, text) {
this.tag('h1', attr, text);
},
h2: function(attr, text) {
this.tag('h2', attr, text);
},
h3: function(attr, text) {
this.tag('h3', attr, text);
},
p: function(attr, text) {
this.tag('p', attr, text);
},
ul: function(list, attr, fn) {
if (typeof attr == 'function') {
fn = attr;
attr = {};
}
this.tag('ul', attr, function(dom){
list.forEach(function(item){
dom.out.push('<li>');
dom.text(fn ? fn(item) : item);
dom.out.push('</li>\n');
});
});
}
};
+52
View File
@@ -0,0 +1,52 @@
require.paths.push(__dirname);
require.paths.push('lib');
var reader = require('reader.js'),
ngdoc = require('ngdoc.js'),
writer = require('writer.js'),
callback = require('callback.js'),
SiteMap = require('SiteMap.js').SiteMap;
var docs = [];
var start;
var work = callback.chain(function(){
start = now();
console.log('Generating Angular Reference Documentation...');
reader.collect(work.waitMany(function(text, file, line){
var doc = new ngdoc.Doc(text, file, line);
docs.push(doc);
doc.parse();
}));
});
var writes = callback.chain(function(){
ngdoc.merge(docs);
docs.forEach(function(doc){
writer.output(doc.id + '.html', doc.html(), writes.waitFor());
});
var metadata = ngdoc.metadata(docs);
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';'], writes.waitFor());
writer.copyImages(writes.waitFor());
writer.copy('index.html', writes.waitFor());
writer.copy('docs.js', writes.waitFor());
writer.copy('docs.css', writes.waitFor());
writer.copy('doc_widgets.js', writes.waitFor());
writer.copy('doc_widgets.css', writes.waitFor());
writer.copy('docs-scenario.html', writes.waitFor());
writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
writer.output('sitemap.xml', new SiteMap(docs).render(), writes.waitFor());
writer.output('robots.txt', 'Sitemap: http://docs.angularjs.org/sitemap.xml\n', writes.waitFor());
writer.copy('syntaxhighlighter/shBrushJScript.js', writes.waitFor());
writer.copy('syntaxhighlighter/shBrushXml.js', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.css', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.js', writes.waitFor());
writer.copy('syntaxhighlighter/shThemeDefault.css', writes.waitFor());
writer.copy('jquery.min.js', writes.waitFor());
});
writes.onDone(function(){
console.log('DONE. Generated ' + docs.length + ' pages in ' +
(now()-start) + 'ms.' );
});
work.onDone(writes);
writer.makeDir('build/docs/syntaxhighlighter', work);
///////////////////////////////////
function now(){ return new Date().getTime(); }
View File
+629
View File
@@ -0,0 +1,629 @@
/**
* All parsing/transformation code goes here. All code here should be sync to ease testing.
*/
var Showdown = require('showdown').Showdown;
var DOM = require('dom.js').DOM;
var htmlEscape = require('dom.js').htmlEscape;
var NEW_LINE = /\n\r?/;
exports.markdown = markdown;
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;
this.scenarios = [];
this.requires = [];
this.param = [];
this.properties = [];
this.methods = [];
self.text.split(NEW_LINE).forEach(function(line){
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
// we found @name ...
// if we have existing name
flush();
atName = match[1];
atText = [];
if(match[3]) atText.push(match[3]);
} else {
if (atName) {
atText.push(line);
}
}
});
flush();
this.shortName = (this.name || '').split(/[\.#]/).pop();
this.id = this.id // if we have an id just use it
|| (((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // try to extract it from file name
|| this.name; // default to name
this.description = markdown(this.description);
this['this'] = markdown(this['this']);
this.exampleDescription = markdown(this.exampleDescription || this.exampleDesc);
return this;
function flush(){
if (atName) {
var text = trim(atText.join('\n'));
if (atName == 'param') {
var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 12 2 34 4 5 5 6 6 3 7 7
if (!match) {
throw new Error("Not a valid 'param' format: " + text);
}
var param = {
name: match[5] || match[4],
description:markdown(text.replace(match[0], match[7])),
type: match[1],
optional: !!match[2],
'default':match[6]
};
self.param.push(param);
} else if (atName == 'returns') {
var match = text.match(/^{([^}=]+)}\s+(.*)/);
if (!match) {
throw new Error("Not a valid 'returns' format: " + text);
}
self.returns = {
type: match[1],
description: markdown(text.replace(match[0], match[2]))
};
} else if(atName == 'description') {
text.replace(/<doc:scenario>([\s\S]*)<\/doc:scenario>/mi,
function(_, scenario){
self.scenarios.push(scenario);
});
self.description = text;
} else if(atName == 'requires') {
self.requires.push(text);
} else if(atName == 'scenario') {
self.scenarios.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.push(property);
} else {
self[atName] = text;
}
}
}
},
html: function(){
var dom = new DOM(),
self = this;
dom.h(this.name, function(){
notice('workInProgress', 'Work in Progress',
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
notice('deprecated', 'Deprecated API', self.deprecated);
(self['html_usage_' + self.ngdoc] || function(){
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom);
dom.example(self.exampleDescription, self.example, self.scenario);
});
return dom.toString();
//////////////////////////
function notice(name, legend, msg){
if (self[name] == undefined) return;
dom.tag('fieldset', {'class':name}, function(dom){
dom.tag('legend', legend);
dom.text(msg);
});
}
},
html_usage_parameters: function(dom) {
dom.h('Parameters', this.param, function(param){
dom.tag('code', function(){
dom.text(param.name);
if (param.optional) {
dom.tag('i', function(){
dom.text('(optional');
if(param['default']) {
dom.text('=' + param['default']);
}
dom.text(')');
});
}
dom.text(' {');
dom.text(param.type);
dom.text('} ');
});
dom.html(param.description);
});
},
html_usage_returns: function(dom) {
var self = this;
if (self.returns) {
dom.h('Returns', function(){
dom.tag('code', '{' + self.returns.type + '}');
dom.text(' ');
dom.html(self.returns.description);
});
}
},
html_usage_this: function(dom) {
var self = this;
if (self['this']) {
dom.h(function(dom){
dom.html("Method's <code>this</code>");
}, function(dom){
dom.html(self['this']);
});
}
},
html_usage_function: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.code(function(){
dom.text(self.name);
dom.text('(');
self.parameters(dom, ', ');
dom.text(');');
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
},
html_usage_directive: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
dom.text('<' + self.element + ' ');
dom.text(self.shortName);
if (self.param.length) {
dom.text('="' + self.param[0].name + '"');
}
dom.text('>\n ...\n');
dom.text('</' + self.element + '>');
});
self.html_usage_parameters(dom);
});
},
html_usage_filter: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.tag('code', function(){
dom.text('{{ ');
dom.text(self.shortName);
dom.text('_expression | ');
dom.text(self.shortName);
self.parameters(dom, ':', true);
dom.text(' }}');
});
});
dom.h('In JavaScript', function(){
dom.tag('code', function(){
dom.text('angular.filter.');
dom.text(self.shortName);
dom.text('(');
self.parameters(dom, ', ');
dom.text(')');
});
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
},
html_usage_formatter: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
if (self.inputType=='select')
dom.text('<select name="bindExpression"');
else
dom.text('<input type="text" name="bindExpression"');
dom.text(' ng:format="');
dom.text(self.shortName);
self.parameters(dom, ':', false, true);
dom.text('">');
});
});
dom.h('In JavaScript', function(){
dom.code(function(){
dom.text('var userInputString = angular.formatter.');
dom.text(self.shortName);
dom.text('.format(modelValue');
self.parameters(dom, ', ', false, true);
dom.text(');');
dom.text('\n');
dom.text('var modelValue = angular.formatter.');
dom.text(self.shortName);
dom.text('.parse(userInputString');
self.parameters(dom, ', ', false, true);
dom.text(');');
});
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
},
html_usage_validator: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
dom.text('<input type="text" ng:validate="');
dom.text(self.shortName);
self.parameters(dom, ':', true);
dom.text('"/>');
});
});
dom.h('In JavaScript', function(){
dom.code(function(){
dom.text('angular.validator.');
dom.text(self.shortName);
dom.text('(');
self.parameters(dom, ', ');
dom.text(')');
});
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
},
html_usage_widget: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
if (self.shortName.match(/^@/)) {
dom.text('<');
dom.text(self.element);
dom.text(' ');
dom.text(self.shortName.substring(1));
if (self.param.length) {
dom.text('="');
dom.text(self.param[0].name);
dom.text('"');
}
dom.text('>\n ...\n</');
dom.text(self.element);
dom.text('>');
} else {
dom.text('<');
dom.text(self.shortName);
(self.param||[]).forEach(function(param){
if (param.optional) {
dom.text(' [' + param.name + '="..."]');
} else {
dom.text(' ' + param.name + '="..."');
}
});
dom.text('></');
dom.text(self.shortName);
dom.text('>');
}
});
});
self.html_usage_parameters(dom);
self.html_usage_returns(dom);
});
},
html_usage_overview: function(dom){
dom.html(this.description);
},
html_usage_service: function(dom){
dom.h('Description', this.description, dom.html);
dom.h('Dependencies', this.requires);
dom.h('Methods', this.methods, function(method){
var signature = (method.param || []).map(property('name'));
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description);
method.html_usage_parameters(dom);
dom.example(method.exampleDescription, method.example, false);
});
});
dom.h('Properties', this.properties, function(property){
dom.h(property.name, function(){
dom.text(property.description);
dom.example(property.exampleDescription, property.example, false);
});
});
},
parameters: function(dom, separator, skipFirst, prefix) {
var sep = prefix ? separator : '';
(this.param||[]).forEach(function(param, i){
if (!(skipFirst && i==0)) {
if (param.optional) {
dom.text('[' + sep + param.name + ']');
} else {
dom.text(sep + param.name);
}
}
sep = separator;
});
}
};
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
function markdown (text) {
if (!text) return text;
var parts = text.split(/(<pre>[\s\S]*?<\/pre>|<doc:example>[\s\S]*?<\/doc:example>)/),
match;
parts.forEach(function(text, i){
if (text.match(/^<pre>/)) {
text = text.replace(/^<pre>([\s\S]*)<\/pre>/mi, function(_, content){
return '<div ng:non-bindable><pre class="brush: js; html-script: true;">' +
content.replace(/</g, '&lt;').replace(/>/g, '&gt;') +
'</pre></div>';
});
} else if (text.match(/^<doc:example>/)) {
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
function(_, before, content, after){
return before + htmlEscape(content) + after;
});
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
function(_, before, content, after){
return before + htmlEscape(content) + after;
});
} else {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
text = text.replace(/{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/gm,
function(_all, url, _2, _3, title){
return '<a href="#!' + url + '">'
+ (url.match(/^angular\./) ? '<code>' : '')
+ (title || url)
+ (url.match(/^angular\./) ? '</code>' : '')
+ '</a>';
});
text = new Showdown.converter().makeHtml(text);
}
parts[i] = text;
});
return parts.join('');
};
//////////////////////////////////////////////////////////
function scenarios(docs){
var specs = [];
docs.forEach(function(doc){
specs.push('describe("' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
specs.push(' browser().navigateTo("index.html#!' + doc.id + '");');
specs.push(' });');
specs.push('');
doc.scenarios.forEach(function(scenario){
specs.push(trim(scenario, ' '));
specs.push('');
});
specs.push('});');
specs.push('');
if (doc.scenario) {
}
});
return specs.join('\n');
}
//////////////////////////////////////////////////////////
function metadata(docs){
var words = [];
docs.forEach(function(doc){
var path = (doc.name || '').split(/(\.|\:\s+)/);
for ( var i = 1; i < path.length; i++) {
path.splice(i, 1);
}
var depth = path.length - 1;
var shortName = path.pop();
words.push({
id: doc.id,
name: doc.name,
depth: depth,
shortName: shortName,
type: doc.ngdoc,
keywords:doc.keywords()
});
});
words.sort(keywordSort);
return words;
}
var KEYWORD_PRIORITY = {
'.guide': 1,
'.guide.overview': 1,
'.angular': 7,
'.angular.Array': 7,
'.angular.Object': 7,
'.angular.directive': 7,
'.angular.filter': 7,
'.angular.formatter': 7,
'.angular.scope': 7,
'.angular.service': 7,
'.angular.validator': 7,
'.angular.widget': 7
};
function keywordSort(a, b){
function mangleName(doc) {
var path = doc.id.split(/\./);
var mangled = [];
var partialName = '';
path.forEach(function(name){
partialName += '.' + name;
mangled.push(KEYWORD_PRIORITY[partialName] || 5);
mangled.push(name);
});
return mangled.join('.');
}
var nameA = mangleName(a);
var nameB = mangleName(b);
return nameA < nameB ? -1 : (nameA > nameB ? 1 : 0);
}
//////////////////////////////////////////////////////////
function trim(text, prefix) {
var MAX = 9999;
var empty = RegExp.prototype.test.bind(/^\s*$/);
var lines = text.split('\n');
var minIndent = MAX;
prefix = prefix || '';
lines.forEach(function(line){
minIndent = Math.min(minIndent, indent(line));
});
for ( var i = 0; i < lines.length; i++) {
lines[i] = prefix + 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;
@@ -32,4 +32,4 @@ li.doc-example-live {
div.syntaxhighlighter {
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
}
}
@@ -1,21 +1,20 @@
(function(){
var angularJsUrl;
var scripts = document.getElementsByTagName("script");
var filename = /(.*\/)angular([^\/]*)/;
for(var j = 0; j < scripts.length; j++) {
var src = scripts[j].src;
if (src && src.match(filename)) {
angularJsUrl = src;
angularJsUrl = src.replace('docs.angularjs.org', 'code.angularjs.org');
}
}
var HTML_TEMPLATE =
'<!doctype html>\n' +
'<html xmlns:ng="http://angularjs.org">\n' +
' <script type="text/javascript" ng:autobind\n' +
' src="' + angularJsUrl + '"></script>\n' +
' <script src="' + angularJsUrl + '" ng:autobind></script>\n' +
' <body>\n' +
'_HTML_SOURCE_\n' +
' </body>\n' +
@@ -33,15 +32,15 @@
var tabs = angular.element(
'<ul class="doc-example">' +
'<li class="doc-example-heading"><h3>Source</h3></li>' +
'<li class="doc-example-source" ng:non-bindable>' +
'<pre class="brush: js; html-script: true; highlight: [' +
'<li class="doc-example-source" ng:non-bindable>' +
'<pre class="brush: js; html-script: true; highlight: [' +
code.hilite + ']; toolbar: false;"></pre></li>' +
'<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
'<li class="doc-example-live">' + exampleSrc +'</li>' +
'<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
'<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>' +
'</ul>');
tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html));
element.html('');
@@ -54,22 +53,29 @@
} catch (e) {
alert(e);
}
return function() {
SyntaxHighlighter.highlight();
};
});
function indent(text) {
var lines = text.split(/\n/);
var lineNo = [];
// remove any leading blank lines
while (lines[0].match(/^\s*$/)) lines.shift();
// remove any trailing blank lines
while (lines[lines.length - 1].match(/^\s*$/)) lines.pop();
var minIndent = 999;
for ( var i = 0; i < lines.length; i++) {
lines[i] = ' ' + lines[i];
lineNo.push(6 + i);
var line = lines[0];
var indent = line.match(/^\s*/)[0];
if (indent !== line && indent.length < minIndent) {
minIndent = indent.length;
}
}
for ( var i = 0; i < lines.length; i++) {
lines[i] = ' ' + lines[i].substring(minIndent);
lineNo.push(5 + i);
}
return {html: lines.join('\n'), hilite: lineNo.join(',') };
};
})();
})();
@@ -7,4 +7,4 @@
</head>
<body>
</body>
</html>
</html>
+302
View File
@@ -0,0 +1,302 @@
/* Common Style */
body {
font-family: Arial, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
}
a {
color: blue;
}
/* Main Layout */
#header {
height: 3.5em;
}
#sidebar,
#main {
position: absolute;
top: 3.5em;
bottom: 0;
margin-top: 1px;
overflow-x: hidden;
}
#sidebar {
width: 13.8em;
padding: 0.8em 0em 1.5em 0.8em;
}
#main {
left: 14.6em;
right: 0;
padding: 1em;
overflow-y: scroll;
}
#api-list {
position: absolute;
top: 3em;
bottom: 1em;
overflow-y: scroll;
padding-right: 0.8em;
}
/* App Header */
#header {
background-color: #F2C200;
border-bottom: 1px solid #957800;
}
#header h1 {
font-weight: normal;
font-size: 30px;
line-height: 30px;
margin: 0;
padding: 10px 10px;
height: 30px;
}
#header .angular {
font-family: "Courier New", monospace;
font-weight: bold;
}
#header h1 a {
color: black;
text-decoration: none;
}
#header h1 a:hover {
text-decoration: underline;
}
/* Main main Style */
#main h1 {
font-family: monospace;
margin-top: 0;
padding-bottom: 5px;
border-bottom: 1px solid #CCC;
}
#main h2 {
margin-top: 1.8em;
}
#main h1 + h2 {
margin-top: 1.3em;
}
#main h3 {
margin-top: 1.5em;
}
#main ul.methods,
#main ul.properties {
margin: 0;
padding-left: 1em;
}
#main ul.methods > li,
#main ul.properties > li {
list-style: none;
padding: 0 10px 10px 10px;
margin-top: 1.5em;
border: 1px solid #000;
}
#main ul.methods > li > h3,
#main ul.properties > li > h3 {
font-family: "Courier New", monospace;
background-color: #E0E0E0;
margin: 0 -10px 5px -10px;
padding: .5em .5em .25em .5em;
border-bottom: 1px solid #888;
}
#main li > p,
#main div.returns > p {
display: inline; /* the top most paragraph should not cause new lines inside lists. */
}
.main-title {
float: right;
}
/* Searchbox & Sidebar Style */
#search-box, #sidebar {
border-right: 1px solid #DDD;
}
#sidebar {
background-color: #EEE;
}
#search-box {
width: 16em;
margin-bottom: 1em;
}
#sidebar a {
text-decoration: none;
}
#sidebar a:hover {
text-decoration: underline;
}
#sidebar ul {
list-style-type: none;
/*TODO(esprehn): Can we just reset globally and not break examples?*/
margin: 0;
padding: 0 0.8em 0 0;
width: 13em;
}
#sidebar ul li {
}
#sidebar ul li a {
display: block;
padding: 2px 2px 2px 4px;
}
#sidebar ul li.selected a {
background-color: #DDD;
border-radius: 5px;
-moz-border-radius: 5px;
border: 1px solid #CCC;
padding: 1px 1px 1px 3px;
}
#sidebar ul li.level-0 {
margin-top: 0.5em;
margin-left: 0em;
font-weight: bold;
font-size: 1.2em;
}
#sidebar ul li.level-0:first-child {
margin-top: 0;
}
#sidebar ul li.level-1.level-angular {
font-family: monospace;
font-weight: normal;
font-size: 1em;
margin-top: 0;
margin-bottom: 0;
}
#sidebar ul li.level-1 {
margin-left: 1em;
margin-top: 5px;
font-size: 1.1em;
font-weight: bold;
}
#sidebar ul li.level-2 {
margin-left: 2em;
font-family: monospace;
}
#sidebar ul li.level-3 {
margin-left: 3em;
font-family: monospace;
}
#sidebar ul li.level-4 {
margin-left: 4em;
font-family: monospace;
}
/* Warning and Info Banners */
.deprecated {
border: 2px solid red;
}
.deprecated legend {
font-weight: bold;
color: red;
}
.workInProgress {
border: 2px solid orange;
}
.workInProgress legend {
font-weight: bold;
color: orange;
}
/* Feedback Link */
#feedback {
float: right;
width: 10em;
text-align: right;
}
/* Live Example Style */
.doc-example-live table td {
padding: 0 1.5em;
}
/* Scrollbars */
::-webkit-scrollbar{
width:0.8em;
margin: 0.2em 0em;
}
::-webkit-scrollbar:hover{
background-color:#eee;
}
::-webkit-scrollbar-thumb{
min-height:0.8em;
min-width:0.8em;
-webkit-border-radius:0.5em;
background-color: #ddd;
}
::-webkit-scrollbar-thumb:hover{
background-color: #bbb;
}
::-webkit-scrollbar-thumb:active{
background-color:#888;
}
#sidebar::-webkit-scrollbar {
background-color:#eee;
}
#main::-webkit-scrollbar {
background-color:#fff;
}
/* Content */
img.right {
float: right;
}
h1, h2, h3, h4, h5 {
clear: both;
}
+43
View File
@@ -0,0 +1,43 @@
DocsController.$inject = ['$location', '$browser', '$window'];
function DocsController($location, $browser, $window) {
this.pages = NG_PAGES;
window.$root = this.$root;
this.getUrl = function(page){
return '#!' + page.id;
};
this.getCurrentPartial = function(){
return './' + this.getTitle() + '.html';
};
this.getTitle = function(){
var hashPath = $location.hashPath || '!angular';
if (hashPath.match(/^!/)) {
this.partialTitle = hashPath.substring(1);
}
return this.partialTitle;
};
this.getClass = function(page) {
var depth = page.depth,
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 ....");
};
}
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.shortName}}</a>
</li>
</ul>
</div>
<div id="main">
<a id="feedback" ng:href="{{getFeedbackUrl()}}">Report an Issue or Ask a Question</a>
<ng:include src="getCurrentPartial()" onload="afterPartialLoaded()"></ng:include>
</div>
</body>
</html>
+1
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
+76
View File
@@ -0,0 +1,76 @@
/**
* All writing related code here. This is so that we can separate the async code from sync code
* for testability
*/
require.paths.push(__dirname);
var fs = require('fs');
var OUTPUT_DIR = "build/docs/";
function output(docs, content, callback){
callback();
}
exports.output = function(file, content, callback){
//console.log('writing', OUTPUT_DIR + file, '...');
fs.writeFile(
OUTPUT_DIR + file,
exports.toString(content),
callback);
};
exports.toString = function toString(obj){
switch (typeof obj) {
case 'string':
return obj;
case 'object':
if (obj instanceof Array) {
obj.forEach(function (value, key){
obj[key] = toString(value);
});
return obj.join('');
} else {
return JSON.stringify(obj);
}
}
return obj;
};
exports.makeDir = function (path, callback) {
var parts = path.split(/\//);
path = '.';
(function next(){
if (parts.length) {
path += '/' + parts.shift();
fs.mkdir(path, 0777, next);
} else {
callback();
}
})();
};
exports.copy = function(filename, callback){
copy('docs/src/templates/' + filename, OUTPUT_DIR + filename, callback);
};
function copy(from, to, callback) {
//console.log('writing', to, '...');
fs.readFile(from, function(err, content){
if (err) return callback.error(err);
fs.writeFile(to, content, callback);
});
}
exports.copyImages = function(callback) {
exports.makeDir(OUTPUT_DIR + '/img', callback.waitFor(function(){
fs.readdir('docs/img', callback.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
if (file.match(/\.(png|gif|jpg|jpeg)$/)) {
copy('docs/img/' + file, OUTPUT_DIR + '/img/' + file, callback.waitFor());
}
});
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);
}
+17 -31
View File
@@ -1,36 +1,22 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<head>
<script type="text/javascript"
src="../angular-debug.js" ng:autobind ng:css="css/angular.css"></script>
<title>angular dev sandbox</title>
<script src="../src/angular-bootstrap.js" ng:autobind></script>
<script>
angular.service('routeConfig', function($route) {
$route.when('/view1', {controller: MyCtrl, template: 'view1.html'});
$route.when('/view2', {controller: MyCtrl, template: 'view2.html'});
function MyCtrl() {};
}, {$inject: ['$route'], $eager: true});
</script>
</head>
<body ng:init="$window.$root = this">
<script>
angular.widget('my:greeter', function(compileElement){
var compiler = this;
compileElement.css('style', 'block');
var salutaitonExp = compileElement.attr('salutation');
var nameExp = compileElement.attr('name');
return function(linkElement){
var salutaitonSpan = angular.element('<span class="salutation"></span');
var nameSpan = angular.element('<span class="name"></span>');
linkElement.append(salutaitonSpan);
linkElement.append(compiler.text(' '));
linkElement.append(nameSpan);
linkElement.append(compiler.text('!'));
this.$watch(salutaitonExp, function(value){
salutaitonSpan.text(value);
});
this.$watch(nameExp, function(value){
nameSpan.text(value);
});
};
});
</script>
<div ng:init="salutation='Hello'; name='World'">
<my:greeter salutation="salutation" name="name"/>
</div>
<body ng:init="$service('$window').$root = this">
<p>
<a href="#/view1">view1</a> | <a href="#/view2">view2</a> | <a href="#">blank</a>
</p>
view: <ng:view></ng:view>
</body>
</html>
</html>
+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 | grep -v '/lib/jasmine' && node docs/src/gen-docs.js
+35
View File
@@ -0,0 +1,35 @@
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
- src/angular-mocks.js
- test/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"
+4 -1
View File
@@ -8,14 +8,17 @@ load:
- 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
- src/angular-mocks.js
- test/mocks.js
- 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:
+2 -1
View File
@@ -13,7 +13,8 @@ load:
- src/scenario/Scenario.js
- src/scenario/output/*.js
- src/scenario/*.js
- test/angular-mocks.js
- src/angular-mocks.js
- test/mocks.js
- test/scenario/*.js
- test/scenario/output/*.js
- test/*.js
+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
+563 -354
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);
};
}
+79 -13
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 && element.length && !(scope = element.data($$scope))) {
element = element.parent();
}
return scope;
}
///////////////////////////////////
//Compiler
//////////////////////////////////
@@ -79,24 +92,25 @@ function Compiler(markup, attrMarkup, directives, widgets){
}
Compiler.prototype = {
compile: function(rawElement) {
rawElement = jqLite(rawElement);
compile: function(element) {
element = jqLite(element);
var index = 0,
template,
parent = rawElement.parent();
parent = element.parent();
if (parent && parent[0]) {
parent = parent[0];
for(var i = 0; i < parent.childNodes.length; i++) {
if (parent.childNodes[i] == rawElement[0]) {
if (parent.childNodes[i] == element[0]) {
index = i;
}
}
}
template = this.templatize(rawElement, index, 0) || new Template();
template = this.templatize(element, index, 0) || new Template();
return function(element, parentScope){
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);
}
+51 -4
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) {
if (!json) return 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 (!isString(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 {
+431 -38
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,264 @@ 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;
var $log, $exceptionHandler;
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 its 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. In other words, evaluates `exp` as angular
* expression in the context of the current scope.
*
* # 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 +372,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 +379,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 {
@@ -154,25 +426,69 @@ function createScope(parent, providers, instanceCache) {
return expressionCompile(expression).call(instance);
}
} catch (e) {
(instance.$log || {error:error}).error(e);
if ($log) $log.error(e);
if (isFunction(exceptionHandler)) {
exceptionHandler(e);
} else if (exceptionHandler) {
errorHandlerFor(exceptionHandler, e);
} else if (isFunction(instance.$exceptionHandler)) {
instance.$exceptionHandler(e);
} else if (isFunction($exceptionHandler)) {
$exceptionHandler(e);
}
}
},
$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 +496,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 +545,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 +589,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,8 +617,27 @@ 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))();
}
$log = instance.$service('$log');
$exceptionHandler = instance.$service('$exceptionHandler');
return instance;
}
+99 -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,113 @@
}
}
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);
try {
globalVars[varKey] = window[prop];
} catch(e) {} //ignore properties that throw exception when accessed (common in FF)
} 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) {
try {
globalVars[key(prop)] = window[prop];
} catch(e) {} //ignore properties that throw exception when accessed (common in FF)
}
// load the js scripts
for (i in Array.prototype.slice.call(arguments, 0)) {
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);
+92 -3
View File
@@ -56,6 +56,24 @@
*/
/**
* @ngdoc overview
* @name angular.mock
* @namespace Namespace for all built-in angular mocks.
*
* @description
* `angular.mock` is a namespace for all built-in mocks that ship with angular and automatically
* replace real services if `angular-mocks.js` file is loaded after `angular.js` and before any
* tests.
*/
angular.mock = {};
/**
* @workInProgress
* @ngdoc service
* @name angular.mock.service.$browser
*/
function MockBrowser() {
var self = this,
expectations = {},
@@ -63,8 +81,25 @@ function MockBrowser() {
this.isMock = true;
self.url = "http://server";
self.lastUrl = self.url; // used by url polling fn
self.pollFns = [];
// register url polling fn
self.onHashChange = function(listener) {
self.pollFns.push(
function() {
if (self.lastUrl != self.url) {
listener();
}
}
);
return listener;
};
self.xhr = function(method, url, data, callback) {
if (angular.isFunction(data)) {
callback = data;
@@ -113,11 +148,20 @@ function MockBrowser() {
self.cookieHash = {};
self.lastCookieHash = {};
self.deferredFns = [];
self.defer = function(fn) {
self.deferredFns.push(fn);
};
self.defer.flush = function() {
while (self.deferredFns.length) self.deferredFns.shift()();
};
}
MockBrowser.prototype = {
poll: function poll(){
angular.foreach(this.pollFns, function(pollFn){
angular.forEach(this.pollFns, function(pollFn){
pollFn();
});
},
@@ -156,7 +200,6 @@ MockBrowser.prototype = {
return this.cookieHash;
}
}
};
angular.service('$browser', function(){
@@ -164,6 +207,52 @@ angular.service('$browser', function(){
});
/**
* @workInProgress
* @ngdoc service
* @name angular.mock.service.$exceptionHandler
*
* @description
* Mock implementation of {@link angular.service.$exceptionHandler} that rethrows any error passed
* into `$exceptionHandler`. If any errors are are passed into the handler in tests, it typically
* means that there is a bug in the application or test, so this mock will make these tests fail.
*
* See {@link angular.mock} for more info on angular mocks.
*/
angular.service('$exceptionHandler', function(e) {
return function(e) {throw e;};
});
/**
* @workInProgress
* @ngdoc service
* @name angular.mock.service.$log
*
* @description
* Mock implementation of {@link angular.service.$log} that gathers all logged messages in arrays
* (one array per logging level). These arrays are exposed as `logs` property of each of the
* level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`.
*
* See {@link angular.mock} for more info on angular mocks.
*/
angular.service('$log', function() {
var $log = {
log: function(){ $log.logs.push(arguments) },
warn: function(){ $log.logs.push(arguments) },
info: function(){ $log.logs.push(arguments) },
error: function(){ $log.logs.push(arguments) }
};
$log.log.logs = [];
$log.warn.logs = [];
$log.info.logs = [];
$log.error.logs = [];
return $log;
});
/**
* Mock of the Date type which has its timezone specified via constroctor arg.
*
@@ -281,7 +370,7 @@ function TzDate(offset, timestamp) {
'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString',
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
angular.foreach(unimplementedMethods, function(methodName) {
angular.forEach(unimplementedMethods, function(methodName) {
this[methodName] = function() {
throw {
name: "MethodNotImplemented",
+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);

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