Compare commits

...

325 Commits

Author SHA1 Message Date
Igor Minar e1cfb99ae9 cutting the 0.9.11 snow-maker release 2011-02-08 17:47:31 -08:00
Igor Minar 1cdc050ce7 release notes for 0.9.11 snow-maker 2011-02-08 17:42:03 -08:00
Igor Minar d600c608e3 markdown pre-processor should strip all the extra indentation
- split trim into trim and indent
- merged my indentation code with trim
- cleaned up some small issues
2011-02-07 23:56:51 -08:00
Igor Minar 86321d1f57 add support for hashSearch redirection and custom redirection functions 2011-02-07 23:56:33 -08:00
Igor Minar 8724e97b7e $route redirection should interpolate variables 2011-02-07 23:56:33 -08:00
Igor Minar bf8013ad57 $location.updateHash should not preserve hashSearch when hashSearch is undefined 2011-02-07 23:56:32 -08:00
Igor Minar a6c45c3e66 add description for ng:controller example 2011-02-07 23:55:46 -08:00
Igor Minar e626f95469 $resource service docs 2011-02-07 23:55:42 -08:00
Igor Minar 2fab1a2da9 docs for $xhr, $xhr.error and $xhr.cache 2011-02-07 23:55:03 -08:00
Igor Minar f3a39a6418 docs should print usage for services that are functions 2011-02-07 23:55:02 -08:00
Igor Minar f00b157841 @property should require type and description should be markdownified 2011-02-07 23:55:02 -08:00
Igor Minar ae20f0c1b3 adding docs for angular.markup and angular.attrMarkup 2011-02-07 23:55:02 -08:00
Igor Minar 9f6c5db2a6 fix script name in watchr-docs.rb 2011-02-07 23:55:02 -08:00
Igor Minar ea9ca651d2 fix how redirection is handled and tested 2011-02-06 09:35:14 -08:00
Igor Minar d6db4b1749 fix typo in $browser mock
Closes #260
2011-02-04 14:23:51 -08:00
Vojta Jina 9798f5e35f mock $log: fixed bug, added some tests
I extracted mock $log factory into stand alone function, so we can access it and test, because this service is rewritten by real service during testing, so we can't access it through angular.$service('$log')...
2011-02-04 14:18:28 -08:00
Igor Minar 94bf24e3b6 fix angular.js file path detection in docs 2011-02-04 14:11:53 -08:00
Igor Minar fbc7f1b454 ng:view should be nestable in ng:include 2011-02-04 14:11:53 -08:00
Igor Minar bf7c9d9900 $route.onChange should return the registered fn 2011-02-04 14:11:52 -08:00
Igor Minar 568574b915 mock's .xhr.flush() should throw exception when there is nothing to flush 2011-02-04 14:11:52 -08:00
Misko Hevery 46d690ff01 smarter normalization of value on option, and htmlParser fixes 2011-02-04 13:44:22 -08:00
Misko Hevery 882f412d57 Added remainder of the cookbook 2011-02-03 20:03:38 -08:00
Misko Hevery 0d4def68ae added more cookbook: work in progress 2011-02-03 20:03:38 -08:00
Misko Hevery d35c1ac8b0 Fixed notifyWhenNoOutstandingRequests() when using JSONP 2011-02-03 20:03:32 -08:00
Misko Hevery 2a9f7b7287 changed order of auto test runner so that JS errors show up last 2011-02-03 13:26:48 -08:00
Misko Hevery 245b60d69a addded cookbook 2011-02-03 13:26:48 -08:00
Misko Hevery b6a01bd27d fixed population of value attribute on option
The value attribute must be populated manually, since different
browsers default to different value of option when not explicitly
defined.
2011-02-03 13:25:01 -08:00
Misko Hevery aaaad298ac corrected typo in the dump function which was preventig serialization of objects 2011-02-03 12:25:43 -08:00
Misko Hevery ba6b68b6ae changed the documentation @example to use <doc:example> 2011-02-01 10:00:09 -08:00
Misko Hevery ed768ebc53 Developer guide documentation 2011-02-01 09:58:37 -08:00
Igor Minar 9fd3dfe49d add support for $route.reload()
Closes 254
2011-02-01 09:35:19 -08:00
Igor Minar d7686a429c add $route.parent for setting parentScope 2011-02-01 09:35:19 -08:00
Igor Minar 6c0cf17404 add redirection support to $route
Closes #217
2011-02-01 09:35:19 -08:00
Igor Minar c648fee5c2 fix $location to handle updates to empty hash well 2011-02-01 09:35:18 -08:00
Igor Minar 9462c78fbf $browser poller should notify $location only once per url change 2011-02-01 09:35:18 -08:00
Igor Minar c4497d60bc fix docs for $window service 2011-02-01 09:35:18 -08:00
Igor Minar 5690627766 upgrading jstd to r845 2011-02-01 09:35:18 -08:00
Igor Minar 2389c71238 fixing $exceptionHandler docs 2011-02-01 09:33:59 -08:00
Igor Minar ce7ab3d1ee add support for 404 handling via $route.otherwise
Closes #217
2011-02-01 09:33:59 -08:00
Igor Minar 7db3b54c1f adding spec for controller-less $route definitions 2011-02-01 09:33:59 -08:00
Igor Minar 21ad176246 fixing $route docs 2011-02-01 09:33:58 -08:00
Igor Minar df1d222dd0 fix older release notes 2011-02-01 09:33:58 -08:00
Igor Minar 7eea26323b upgrading jstd to r844 2011-01-31 2011-01-31 12:46:31 -08:00
Igor Minar f0fb4a3928 jqlite should not generate exceptions when accessing attributes on Document 2011-01-30 14:43:53 -08:00
Igor Minar 0144fa1ca9 fixing angular.Scope. docs 2011-01-30 14:43:03 -08:00
Igor Minar b612826158 preparing the 0.9.11 snow-maker iteration 2011-01-30 14:40:48 -08:00
Misko Hevery e5a3acacad corrected sitemap.xml format 2011-01-27 09:53:20 -08:00
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
Igor Minar 5f0af2cd0e cutting the 0.9.3 cold-resistance release 2010-11-10 22:15:16 -08:00
Igor Minar 7411b24812 updating the release notes for 0.9.3 release 2010-11-10 22:14:16 -08:00
Igor Minar ae5f6f48b4 scenario repeater should match only visible stuff 2010-11-10 21:04:44 -08:00
Igor Minar c5b2bf083c Make ng:repeat expose $position.
- $position is a textual representation of the position of
  repeated item ('first', 'middle', 'last')
- added specs for $index
2010-11-10 21:04:44 -08:00
Misko Hevery 0499c47270 added ng:switch-when-default; changed $watch to always fire on init. (may be backward incompatible) 2010-11-10 21:01:17 -08:00
Misko Hevery 43a4ff4cdf Example snippets now have full html snippets 2010-11-10 21:01:05 -08:00
Misko Hevery 6b8ed42670 Added Directives 2010-11-10 12:02:49 -08:00
Elliott Sprehn c57df3dc77 Make the docs look a little nicer 2010-11-10 11:01:30 -08:00
Igor Minar 6d53808475 improve jsdocs
- improve json filter example
- improve filter overview doc
- improving validator overview jsdocs
- simplify number filter examples and make them live + add specs
- various doc fixes
2010-11-09 22:33:27 -08:00
Misko Hevery a7e8a503fd proper trimming of leading whitespace; url restriction to angular.* 2010-11-09 22:27:37 -08:00
Igor Minar 324694a58b Better example widget
- syntax highlighting
- tabless design
- rename widget to doc:example
- rename widget files (wiki_widget.* -> doc_widget.*)
- example section is now optional
2010-11-09 22:27:31 -08:00
Igor Minar effcd340e9 updating watchr-docs.rb 2010-11-09 21:44:51 -08:00
Igor Minar 264f960800 added spec for auto bootstrap with #autobind 2010-11-09 19:58:42 -08:00
Elliott Sprehn 257e97a65f Support substring matching of bindings with repeater(). Closes #123 2010-11-09 13:39:12 -08:00
Misko Hevery c048f0d8e8 Added formatter documentation. 2010-11-09 09:15:29 -08:00
Elliott Sprehn 96e37a0866 Add key/value jQuery methods in a loop. Support 'css' method.
Closes #134
2010-11-09 08:02:49 -08:00
Vojta Jina 5062d32621 Updated parser tests to use jasmine expects
And some formatting as well and a bit of rafactor...
2010-11-09 07:38:03 -08:00
Vojta Jina d458f31711 Updated filters tests to use jasmine expects
And some small formatting...
2010-11-09 07:38:03 -08:00
Igor Minar fc9ce9ec07 make angular.String.toDate consider all time fractions as optional 2010-11-08 22:49:30 -08:00
Igor Minar da17c61444 angular.Date.toString should use toISOString if available 2010-11-08 22:49:30 -08:00
Igor Minar e5c135ac50 Support ISO 8601 extended datetime format troughout angular.
Support ISO 8601 extended format datetime strings (YYYY-MM-DDTHH:mm:ss.SSSZ) as defined
  in EcmaScript 5 throughout angular. This means that the following apis switched from
  YYYY-MM-DDTHH:mm:ssZ to YYYY-MM-DDTHH:mm:ss.SSSZ (note the added millis) when representing dates:
  - angular.Date.toString
  - angular.String.toDate
  - JSON serialization and deserialization (used by json filter, $xhr and $resource)
2010-11-08 22:49:30 -08:00
Igor Minar 1a43f36e23 Add isDate method + fix old code 2010-11-08 22:49:29 -08:00
Igor Minar 1c305dc67a fixing spec description for the date filter 2010-11-08 22:46:41 -08:00
Igor Minar a397645537 date filter should accept ISO 8601 formatted string as input
Closes #125
2010-11-08 22:46:41 -08:00
Igor Minar f077649f48 TzDate should support various UTC methods 2010-11-08 22:46:41 -08:00
Vojta Jina f3ac2cd434 Update AngularSpec tests to use BDD style - jasmine expect 2010-11-07 14:42:04 -08:00
Vojta Jina 7779630989 Added tests for angular.service
- should allow to override a service
- should preserve angular properties on override
- should not preserve non-angular properties on override
2010-11-07 14:42:03 -08:00
Vojta Jina 00ca67e4be Issue #51: Update extensionMap()
If user override existing extension, angular properties ($) will be preserved.

This piece of logic could be refactored into separate method:
Something like we have extend(), addMissingProperties() - I can't find a name
for this method...

Closes #51
2010-11-07 14:42:03 -08:00
Misko Hevery 91b6c5f7ff Added documentation for validators.
BACKWARD INCOMPATIBLE: removed ssn validators, since it is unlikely that most people will need it and if they do, they can added it thorough RegExp
2010-11-07 13:06:55 -08:00
Vojta Jina 5be325a0c1 Refactored toJsonArray(), added isBoolean() function 2010-11-05 21:39:00 -07:00
Vojta Jina b7027b9d87 Updated toJson() to not serialize window/document objects.
The reason to void these to objects is that they cause all sorts
of problems like exceptions being thrown and infinite loops occuring
when we iterate over object properties.
2010-11-05 21:39:00 -07:00
Vojta Jina fe8353bc5e Changed toJson() to not ignore $ properties 2010-11-05 21:39:00 -07:00
Vojta Jina c780030c6e Json - corrected test descriptions and changed to BDD style expect() 2010-11-05 21:39:00 -07:00
Misko Hevery d5e9f38f3d fix error in json parser which did not allow 1.2E10 as number 2010-11-05 16:41:36 -07:00
Misko Hevery dc66687149 added tests for documentation 2010-11-05 15:05:24 -07:00
Misko Hevery 3d6a099d6e changed to showdown from markup. added validator overview 2010-11-05 13:32:37 -07:00
Misko Hevery 8767e766d1 eclipse settings for auto run of docs 2010-11-04 14:25:11 -07:00
Misko Hevery 47066e70e1 added documentation for ng:include and ng:widget and test for doc collector. 2010-11-04 14:24:31 -07:00
Misko Hevery c0d30aedfc added LICENSE to IDEA 2010-11-04 11:07:49 -07:00
Misko Hevery b246d6e2ab rename Index.js to index.js 2010-11-04 11:02:45 -07:00
Igor Minar 3b04b48b7c preparations for the 0.9.3 cold-resistance iteration 2010-11-03 13:09:52 -07:00
Igor Minar 0462422089 cutting the 0.9.2 faunal-mimicry release 2010-11-03 13:06:45 -07:00
Igor Minar bb460d6355 updating release notes 2010-11-03 13:05:12 -07:00
Igor Minar bbb19a4d16 fixing docs scenario runner template 2010-11-03 12:55:17 -07:00
Elliott Sprehn ee5e881b9e Fix JSON serialization breakage in WebKit browsers 2010-11-03 11:47:06 -07:00
Elliott Sprehn 690dfe000b Serialize RegExp to string in JSON. Closes #119. 2010-11-03 11:10:51 -07:00
Igor Minar 91c835dc0e fix dslSpec for IE 2010-11-03 10:41:37 -07:00
Igor Minar dfa8baf59a hide example for angular.filter 2010-11-03 10:02:20 -07:00
Igor Minar a8efd43d25 doc parser should recognize the 'defaults' syntax for params 2010-11-03 09:47:23 -07:00
Igor Minar 96abeb1074 adding watchr-docs.rb 2010-11-03 09:47:22 -07:00
Misko Hevery 5159eb7635 fix the linky filter 2010-11-03 09:47:22 -07:00
Igor Minar 1ba8c2a33a more documentation for filters 2010-11-03 09:47:22 -07:00
Misko Hevery 659af29adb jsdoc parser + generator + viewer + scenario runner
- parse jsdocs from source code
- generate prerendered (markdown + mustache) partials
- generate json
- generate scenario runner for examples in docs
- basic angular doc viewer
2010-11-03 09:47:22 -07:00
Igor Minar 1fe7e3a130 add jsdocs for angular and filter namespaces + all filters 2010-11-03 09:47:21 -07:00
Igor Minar aec3c8478c binding() should return value for input/text area, innerHTML for the rest 2010-11-03 09:47:21 -07:00
Igor Minar 5c887ddb66 adding textarea() DSL for scenario runner 2010-11-03 09:47:21 -07:00
Igor Minar 0bd4a473a7 adding regular expressions support for binding() DSL 2010-11-03 09:44:54 -07:00
Elliott Sprehn dcf76e6816 Provide better sandbox error messages, and disallow running from file:// URLs 2010-11-02 11:27:54 -07:00
Elliott Sprehn 56a3d52f45 Make future names consistent and handle falsy values in jQuery generated methods properly 2010-11-02 11:20:41 -07:00
Elliott Sprehn faa7d81b67 Add browser().reload() to simulate a refresh from a user 2010-11-01 17:24:24 -07:00
Elliott Sprehn 6bb2cd6ee2 Provide browser DSL with location() to expect the iframe URL parts. Also move navigateTo() under the browser DSL. 2010-11-01 15:21:37 -07:00
Vojta Jina 2d61040fb0 Small refactor in $location service
Added new inner method updateLastLocation()
2010-10-31 21:37:54 -07:00
Vojta Jina 99f25050a3 Fixing issue #98 (infinite loop when location hash set empty)
Added tests and fixed the issue.

Closes #98
2010-10-31 21:37:33 -07:00
Misko Hevery ba5f8ee27f fix typo, and change a list to string for more efficient compression. 2010-10-31 15:04:30 -07:00
Elliott Sprehn 9a532002cf Auto generate all the jQuery get/set methods 2010-10-29 12:19:22 -07:00
Elliott Sprehn 5524d2b0fb Check if file exists (not a 404) and that document is accessible and not using file:// URLs in Application 2010-10-29 11:40:56 -07:00
Igor Minar d4839bac32 adding watchr config file watchr.rb 2010-10-29 10:47:51 -07:00
Igor Minar 602369c6a2 creating logs/ and tmp/ dirs 2010-10-29 10:47:06 -07:00
Andres Ornelas 34909520ae add optional label to dsl with selectors to improve test and output readability
e.g.
Before:
   code:   element('.actions ul li a').click();
   output: element .actions ul li a click
After
   code:   element('.actions ul li a', "'Configuration' link").click();
   output: element 'Configuration' link ( .actions ul li a ) click
2010-10-28 15:21:02 -07:00
Elliott Sprehn 92e31b556f Correctly fail tests if no binding matches and add better test cases for failure behavior. 2010-10-27 17:56:44 -07:00
Misko Hevery 62c0e5c460 Fix failing tests for ie, and mark elements as ng-widget, ng-directive, and ng-binding 2010-10-27 15:42:46 -07:00
Igor Minar c67af8a038 rename src/Parser.js to src/parser.js 2010-10-27 15:32:30 -07:00
Elliott Sprehn 2da1de5a6d Revert performance optimization of using setTimeout in SpecRunner every 10 steps. Breaks loading partials 2010-10-27 13:41:31 -07:00
Igor Minar ee8465bf10 updating version.yaml for the 0.9.2 iteration 2010-10-27 07:38:18 -07:00
Misko Hevery 9c6794a044 start new release 0.9.2 faunal-mimicry 2010-10-26 22:48:34 -07:00
188 changed files with 17583 additions and 3087 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&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,"/>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/build&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs&quot; type=&quot;2&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
</launchConfiguration>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LAUNCH_CONFIGURATION_BUILD_SCOPE" value="${none}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
</launchConfiguration>
+4 -1
View File
@@ -1,4 +1,7 @@
build/
angularjs.netrc
jstd.log
.DS_Store
.DS_Store
regression/temp*.html
performance/temp*.html
.idea/workspace.xml
-1
View File
@@ -1 +0,0 @@
workspace.xml
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Settings><!--This file was automatically generated by Ruby plugin.
You are allowed to:
1. Remove rake task
2. Add existing rake tasks
To add existing rake tasks automatically delete this file and reload the project.
--><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Compile JavaScript" fullCmd="compile" taksId="compile" /><RakeTask description="Generate Externs" fullCmd="compileexterns" taksId="compileexterns" /><RakeTask description="Lint" fullCmd="lint" taksId="lint" /><RakeGroup description="" fullCmd="" taksId="server"><RakeTask description="Run JsTestDriver Server" fullCmd="server:start" taksId="start" /><RakeTask description="Run JavaScript tests against the server" fullCmd="server:test" taksId="test" /></RakeGroup><RakeTask description="Run JavaScript tests" fullCmd="test" taksId="test" /><RakeTask description="" fullCmd="default" taksId="default" /></RakeGroup></Settings>
Generated
+67
View File
@@ -0,0 +1,67 @@
IMPORTANT: THIS IS TO CERTIFY THE RIGHT TO USE THE JETBRAINS SOFTWARE PRODUCT, GRANTED BY JETBRAINS S.R.O. UNDER THE TERMS AND CONDITIONS OF THE LICENSE AGREEMENT INCLUDED WITH THE SOFTWARE. PLEASE SAVE A COPY OF THIS EMAIL FOR FUTURE REFERENCES.
========LICENSE DETAILS========
Type: Open Source License
Reference No*: LC-93161-D352729080
Date of Issue: 3 November 2010
Expiration Date: 3 November 2011
Number of Authorized Users: not limited
* Please quote this reference when contacting JetBrains
===========LICENSEE============
Name: angularjs
Customer ID: 93161
Address:
=======SOFTWARE PRODUCT========
Product Name: WebStorm
Licensed Version: 1.0 and any new product release which is made generally available before 3 November 2011
The software is shipped electronically and is available for download from:
http://www.jetbrains.com/webstorm/download/
Your WebStorm license includes a 1-year upgrade subscription qualifying you for free upgrades to any new versions of WebStorm released by JetBrains during your upgrade subscription period starting on your license issue date and ending on 3 November 2011. To check availability of the new versions of WebStorm, please visit http://www.jetbrains.com.
For running any new version of WebStorm released by JetBrains during your upgrade subscription period, please use the included below licensing details.
You can renew your upgrade subscription before or after its expiration. Your new subscription period will start on the date following the expiration date of your current upgrade subscription.
=========INSTALLATION==========
Run WebStorm and follow the Installation Wizard's instructions. To register for use of the software or change your existing registration details, go to Help/Register menu of the software and enter the included below the User Name and License Key(s) into the registration dialog:
User Name: angularjs
===== LICENSE BEGIN =====
93161-03112010
00000jBsEx59XVlc79fV"aAqWXQ09e
jQsg5TNp5X4HGhc10LNBdu!!ejRcFG
7h3S6T09YcRWs23TH0RgaM87!HqmQo
===== LICENSE END =====
===DOCUMENTATION AND SUPPORT===
WebStorm documentation:
http://www.jetbrains.com/webstorm/documentation/
Available support resources:
http://www.jetbrains.com/support/
Technical support contact:
support@jetbrains.com
Contact for the license renewal requests:
opensource@jetbrains.com
For questions, please contact:
sales@jetbrains.com
JetBrains Sales Team
http://www.jetbrains.com
"Develop with pleasure!"
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RUBY_MODULE" version="4">
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
+1 -4
View File
@@ -3,10 +3,7 @@
<component name="DependencyValidationManager">
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</component>
<component name="ProjectDetails">
<option name="projectName" value="master" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Ruby SDK 1.8.7 (/usr/bin/ruby)" project-jdk-type="RUBY_SDK" />
<component name="ProjectRootManager" version="2" />
<component name="SvnBranchConfigurationManager">
<option name="mySupportsUserInfoFilter" value="true" />
</component>
+1 -1
View File
@@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/master.iml" filepath="$PROJECT_DIR$/.idea/master.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/angular.js.iml" filepath="$PROJECT_DIR$/.idea/angular.js.iml" />
</modules>
</component>
</project>
+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="USE_SAME_INDENTS" value="true" />
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="2" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</value>
</option>
<ADDITIONAL_INDENT_OPTIONS fileType="js">
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="jsp">
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="sass">
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="xml">
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="yml">
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>
+15
View File
@@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="gen_docs" type="BashConfigurationType" factoryName="Bash">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PARENT_ENVS" value="true" />
<envs />
<module name="angular.js" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/gen_docs.sh" />
<option name="PARAMETERS" value="" />
<RunnerSettings RunnerId="BashRunner" />
<ConfigurationWrapper RunnerId="BashRunner" />
<method />
</configuration>
</component>
+15
View File
@@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="rake compile" type="BashConfigurationType" factoryName="Bash">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="INTERPRETER_PATH" value="/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PARENT_ENVS" value="true" />
<envs />
<module name="angular.js" />
<option name="SCRIPT_NAME" value="/usr/bin/rake" />
<option name="PARAMETERS" value="compile" />
<RunnerSettings RunnerId="BashRunner" />
<ConfigurationWrapper RunnerId="BashRunner" />
<method />
</configuration>
</component>
Generated
+1 -2
View File
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
+10
View File
@@ -10,6 +10,16 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/docs.launch</value>
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
+2 -1
View File
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="test/" kind="src" path="src"/>
<classpathentry kind="src" path="src/test"/>
<classpathentry excluding="docs-data.js|docs-scenario.js" kind="src" path="docs"/>
<classpathentry excluding="test/" kind="src" path="test"/>
<classpathentry kind="src" path="test/test"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
<classpathentry kind="output" path=""/>
</classpath>
@@ -0,0 +1,16 @@
#Mon Jan 24 10:31:47 PST 2011
activeContentFilterList=*.makefile,makefile,*.Makefile,Makefile,Makefile.*,*.mk,MANIFEST.MF
addNewLine=true
convertActionOnSaave=AnyEdit.CnvrtTabToSpaces
eclipse.preferences.version=1
inActiveContentFilterList=
javaTabWidthForJava=true
org.eclipse.jdt.ui.editor.tab.width=2
projectPropsEnabled=false
removeTrailingSpaces=true
replaceAllSpaces=false
replaceAllTabs=false
saveAndAddLine=true
saveAndConvert=true
saveAndTrim=true
useModulo4Tabs=false
+287
View File
@@ -1,3 +1,289 @@
<a name="0.9.11"><a/>
# <angular/> 0.9.11 snow-maker (2011-02-08) #
### Documentation
- completed migration of docs from the wiki site to
[http://docs.angularjs.org/](http://docs.angularjs.org/)
- many, but by far not all, docs were updated, improved and cleaned up
### Features
- [`$route`](http://docs.angularjs.org/#!angular.service.$route) service now supports these
features:
- route not found handling via `#otherwise()`
- redirection support via `#when('/foo', {redirectTo: '/bar'})` (including param interpolation)
- setting the parent scope for scopes created by the service via `#parent()`
- reloading the current route via `#reload()`
### Bug Fixes
- <option> value attribute gets clobbered when the element contains new line character(s).
- <ng:view> widget now works when nested inside an <ng:include> widget
- other various small fixes
### Breaking changes
- mock [`$browser`](http://docs.angularjs.org/#!angular.mock.service.$browser) now throws an
exception if the `flush()` method is called when there are no requests to be flushed. If you
experience `No xhr requests to be flushed!` errors in your tests, it's because you called
`$browser.xhr.flush()` unexpectedly. To make the error go away, either make sure your code makes a
request via the `$xhr` service or remove all unneeded `flush()` calls.
<a name="0.9.10"><a/>
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
### Features
- new [`ng:view`](http://docs.angularjs.org/#!angular.widget.ng:view) widget to simplify integration
with the `$route` service
- the content of all standard HTML widgets is now being processed
(e.g. `<button>{{foo}}</button>` works now) (commit 1d7b9d56)
- new [`$log`](http://docs.angularjs.org/#!angular.mock.service.$log) and
[`$exceptionHandler`](http://docs.angularjs.org/#!angular.mock.service.$exceptionHandler) service
mocks now part of `angular-mocks.js` (commit f5d08963)
### Bug Fixes
- <select> (one/multiple) could not chose from a list of objects (commit 347be5ae)
- null and other falsy values should not be rendered in the view (issue #242)
### Docs
- rewrite of several major portions of angular.service.*, angular.Array.*, angular.Object.* docs
- added support for [sitemap]((http://docs.angularjs.org/sitemap.xml) to make the docs indexable by
search crawlers
- transition of Developer Guide docs from the wiki into docs.angularjs.org
- lots of improvements related to formatting of the content of docs.anguarjs.org
<a name="0.9.9"><a/>
# <angular/> 0.9.9 time-shift (2011-01-13) #
### Security
- Added a just in case security check for JSON parsing. (commit 5f080193)
- Completed security review with the Google Security Team.
### Performance
- $location and $cookies services are now lazily initialized to avoid the polling overhead when
not needed.
- $location service now listens for `onhashchange` events (if supported by browser) instead of
constant polling. (commit 16086aa3)
- input widgets known listens on keydown events instead of keyup which improves perceived
performance (commit 47c454a3)
- angular boots significantly sooner by listening for DOMContentLoaded event instead of
window.load when supported by browser (commit c79aba92)
- new service $updateView which may be used in favor of $root.$eval() to run a complete eval on
the entire document. This service bulks and throttles DOM updates to improve performance.
(commit 47c454a3)
### Docs
- Major improvements to the doc parser (commit 4f22d686)
- Docs now offline enabled (all dependencies are bundled in the tarball) (commit 4f5d5029)
- Added support for navigating the docs app with keyboard shortcuts (tab and ctrl+alt+s)
### Bugfixes
- `angular.Object.equals` now properly handless comparing an object with a null (commit b0be87f6)
- Several issues were addressed in the `$location` service (commit 23875cb3)
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
### Breaking changes
- API for accessing registered services — `scope.$inject` — was renamed to
[`scope.$service`](http://docs.angularjs.org/#!angular.scope.$service). (commit b2631f61)
- Support for `eager-published` services was removed. This change was done to make explicit
dependency declaration always required in order to allow making relatively expensive services
lazily initialized (e.g. $cookie, $location), as well as remove 'magic' and reduce unnecessary
scope namespace pollution. (commit 3ea5941f)
Complete list of affected services:
- $location
- $route
- $cookies
- $window
- $document
- $exceptionHandler
- $invalidWidgets
To temporarily preserve the 'eager-published' status for these services, you may use `ng:init`
(e.g. `ng:init="$location = $service('$location'), ...`) in the view or more correctly create
a service like this:
angular.service('published-svc-shim', function($location, $route, $cookies, $window,
$document, $exceptionHandler, $invalidWidgets) {
this.$location = $location;
this.$route = $route;
this.$cookies = $cookies;
this.$window = $window;
this.$document = $document;
this.$exceptionHandler = $exceptionHandler;
this.$invalidWidgets = $invalidWidgets;
}, {$inject: ['$location', '$route', '$cookies', '$window', '$document', '$exceptionHandler',
'$invalidWidgets'],
$eager: true});
- In the light of the `eager-published` change, to complete the cleanup we renamed `$creation`
property of services to `$eager` with its value being a boolean.
To transition, please rename all `$creation: 'eager'` declarations to `$eager: true`.
(commit 1430c6d6)
- `angular.foreach` was renamed to `angular.forEach` to make the api consistent. (commit 0a6cf70d)
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
<a name="0.9.8"><a/>
# <angular/> 0.9.8 astral-projection (2010-12-23) #
### Docs/Getting started
- angular-seed project to get you hacking on an angular apps quickly
https://github.com/angular/angular-seed
### Performance
- Delegate JSON parsing to native parser (JSON.parse) if available
### Bug Fixes
- Ignore input widgets which have no name (issue #153)
<a name="0.9.7"><a/>
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
### Bug Fixes
- $defer service should always call $eval on the root scope after a callback runs (issue #189)
- fix for failed assignments of form obj[0].name=value (issue #169)
- significant parser improvements that resulted in lower memory usage
(commit 23fc73081feb640164615930b36ef185c23a3526)
### Docs
- small docs improvements (mainly docs for the $resource service)
### Breaking changes
- Angular expressions in the view used to support regular expressions. This feature was rarely
used and added unnecessary complexity. It not a good idea to have regexps in the view anyway,
so we removed this support. If you had any regexp in your views, you will have to move them to
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
<a name="0.9.6"><a/>
# <angular/> 0.9.6 night-vision (2010-12-06) #
### Security
- several improvements in the HTML sanitizer code to prevent code execution via `href`s and other
attributes.
Commits:
- 41d5938883a3d06ffe8a88a51efd8d1896f7d747
- 2bbced212e2ee93948c45360fee00b2e3f960392
### Docs
- set up http://docs.angularjs.org domain, the docs for the latest release will from now on be
deployed here.
- docs app UI polishing with dual scrolling and other improvements
### Bug Fixes
- `select` widget now behaves correctly when it's `option` items are created via `ng:repeat`
(issue #170)
- fix for async xhr cache issue #152 by adding `$browser.defer` and `$defer` service
### Breaking Changes
- Fix for issue #152 might break some tests that were relying on the incorrect behavior. The
breakage will usually affect code that tests resources, xhr or services/widgets build on top of
these. All that is typically needed to resolve the issue is adding a call to
`$browser.defer.flush()` in your test just before the point where you expect all cached
resource/xhr requests to return any results. Please see 011fa39c2a0b5da843395b538fc4e52e5ade8287
for more info.
- The HTML sanitizer is slightly more strinct now. Please see info in the "Security" section above.
<a name="0.9.5"><a/>
# <angular/> 0.9.5 turkey-blast (2010-11-25) #
### Docs
- 99% of the content from the angular wiki is now in the docs
### Api
- added `angular.Array.limitTo` to make it easy to select first or last few items of an array
<a name="0.9.4"><a/>
# <angular/> 0.9.4 total-recall (2010-11-18) #
### Docs
- searchable docs
- UI improvements
- we now have ~85% of the wiki docs migrated to ng docs
- some but not all docs were updated along the way
### Api
- ng:include now supports `onload` attribute (commit cc749760)
### Misc
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
<a name="0.9.3"><a/>
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
### Docs
- prettier docs app with syntax highlighting for examples, etc
- added documentation, examples and scenario tests for many more apis including:
- all directives
- all formatters
- all validators
- some widgets
### Api
- date filter now accepts strings that angular.String.toDate can convert to Date objects
- angular.String.toDate supports ISO8061 formated strings with all time fractions being optional
- ng:repeat now exposes $position with values set to 'first', 'middle' or 'last'
- ng:switch now supports ng:switch-default as fallback switch option
### Breaking changes
- we now support ISO 8601 extended format datetime strings (YYYY-MM-DDTHH:mm:ss.SSSZ) as defined
in EcmaScript 5 throughout angular. This means that the following apis switched from
YYYY-MM-DDTHH:mm:ssZ to YYYY-MM-DDTHH:mm:ss.SSSZ (note the added millis) when representing dates:
- angular.Date.toString
- angular.String.fromDate
- JSON serialization and deserialization (used by json filter, $xhr and $resource)
- removed SSN validator. It's unlikely that most people will need it and if they do, it can be added
simple RegExp validator.
<a name="0.9.2"><a/>
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
### Docs
- created documentation framework based on jsdoc syntax (commit 659af29a)
- jsdoc parser
- template generator
- json generator
- angular doc viewer app
- scenario runner for all example code
- documentation for all angular filters (commits 1fe7e3a1 & 1ba8c2a33)
- docs
- example code
- scenario tests for example code
### Testability
#### Scenario Runner
- binding DSL in Scenario can now match bindings without specifying filters
- dsl statements now accept a label argument to make test output more readable (issue #94)
- dsl element() statement now implements most of the jQuery API (issue #106)
- new browser() dsl statement for getting info about the emulated browser running the app
(issue #109)
- scenario runner is now compatible with IE8 (issue #93)
- scenarior runner checks if URL would return a non-success status code (issue #100)
- binding() DSL now accepts regular expressions
- new textarea() scenario runner DSL for entering text into textareas
### Misc
- lots of small bugfixes
### Breaking changes
#### Scenario Runner
- navigating to about:blank is no longer supported. It results in a sandbox error
- navigateTo() is now browser().navigateTo(). Old code must be updated
- file:// URLs are no longer supported for running a scenario. You must use a web server that
implements HEAD
<a name="0.9.1"><a/>
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
### Security
@@ -24,6 +310,7 @@
- html filter now sanitizes html content for XSS attacks which may result in different behavior
<a name="0.9.0"><a/>
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
### Security
+24 -4
View File
@@ -6,7 +6,7 @@ ANGULAR = [
'src/Compiler.js',
'src/Scope.js',
'src/Injector.js',
'src/Parser.js',
'src/parser.js',
'src/Resource.js',
'src/Browser.js',
'src/sanitizer.js',
@@ -156,8 +156,14 @@ task :compile => [:init, :compile_scenario, :generate_ie_compat] do
end
desc 'Generate docs'
task :docs do
`node docs/src/gen-docs.js`
end
desc 'Create angular distribution'
task :package => [:clean, :compile] do
task :package => [:clean, :compile, :docs] do
v = YAML::load( File.open( 'version.yaml' ) )['version']
match = v.match(/^([^-]*)(-snapshot)?$/)
version = match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : '')
@@ -168,7 +174,7 @@ task :package => [:clean, :compile] do
FileUtils.rm_r(path_to('pkg'), :force => true)
FileUtils.mkdir_p(pkg_dir)
['test/angular-mocks.js',
['src/angular-mocks.js',
path_to('angular.js'),
path_to('angular.min.js'),
path_to('angular-ie-compat.js'),
@@ -178,6 +184,20 @@ task :package => [:clean, :compile] do
FileUtils.cp(src, pkg_dir + '/' + dest)
end
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{version}"
File.open("#{pkg_dir}/docs-#{version}/index.html", File::RDWR) do |f|
text = f.read
f.rewind
f.write text.sub('angular.min.js', "angular-#{version}.min.js")
end
File.open("#{pkg_dir}/docs-#{version}/docs-scenario.html", File::RDWR) do |f|
text = f.read
f.rewind
f.write text.sub('angular-scenario.js', "angular-scenario-#{version}.js")
end
%x(tar -czf #{path_to(tarball)} -C #{path_to('pkg')} .)
puts "Package created: #{path_to(tarball)}"
@@ -256,5 +276,5 @@ end
# returns path to the file in the build directory
#
def path_to(filename)
return File.join(BUILD_DIR, filename)
return File.join(BUILD_DIR, *filename)
end
+5
View File
@@ -8,6 +8,11 @@ body {
font-size: 14px;
}
#system-error {
font-size: 1.5em;
text-align: center;
}
#json, #xml {
display: none;
}
+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 {
+15
View File
@@ -0,0 +1,15 @@
@workInProgress
@ngdoc overview
@name angular.attrMarkup
@description
Attribute markup extends the angular compiler in a very similar way as {@link angular.markup} except
that it allows you to modify the state of the attribute text rather then the contest of a node.
<pre>
angular.attrMarkup('extraClass', function(attrValue, attrName, element){
if (attrName == 'additional-class') {
element.addClass(attrValue);
}
});
</pre>
+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.
+87
View File
@@ -0,0 +1,87 @@
@workInProgress
@ngdoc overview
@name angular.filter
@namespace Namespace for all filters.
@description
# Overview
Filters are a standard way to format your data for display to the user. For example, you
might have the number 1234.5678 and would like to display it as US currency: $1,234.57.
Filters allow you to do just that. In addition to transforming the data, filters also modify
the DOM. This allows the filters to for example apply css styles to the filtered output if
certain conditions were met.
# Standard Filters
The Angular framework provides a standard set of filters for common operations, including:
{@link angular.filter.currency currency}, {@link angular.filter.json json},
{@link angular.filter.number number}, and {@link angular.filter.html html}. You can also add
your own filters.
# Syntax
Filters can be part of any {@link angular.scope} evaluation but are typically used with
{{bindings}}. Filters typically transform the data to a new data type, formating the data in
the process. Filters can be chained and take optional arguments. Here are few examples:
* No filter: {{1234.5678}} => 1234.5678
* Number filter: {{1234.5678|number}} => 1,234.57. Notice the “,” and rounding to two
significant digits.
* Filter with arguments: {{1234.5678|number:5}} => 1,234.56780. Filters can take optional
arguments, separated by colons in a binding. To number, the argument “5” requests 5 digits
to the right of the decimal point.
# Writing your own Filters
Writing your own filter is very easy: just define a JavaScript function on `angular.filter`.
The framework passes in the input value as the first argument to your function. Any filter
arguments are passed in as additional function arguments.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
the DOM in addition to transforming the input.
@example
The following example filter reverses a text string. In addition, it conditionally makes the
text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
modification).
<doc:example>
<doc:source>
<script type="text/javascript">
angular.filter('reverse', function(input, uppercase, color) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
if (uppercase) {
out = out.toUpperCase();
}
if (color) {
this.$element.css('color', color);
}
return out;
});
</script>
<input name="text" type="text" value="hello" /><br>
No filter: {{text}}<br>
Reverse: {{text|reverse}}<br>
Reverse + uppercase: {{text|reverse:true}}<br>
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
</doc:source>
<doc:scenario>
it('should reverse text', function(){
expect(binding('text|reverse')).toEqual('olleh');
input('text').enter('ABC');
expect(binding('text|reverse')).toEqual('CBA');
});
</doc:scenario>
</doc:example>
+82
View File
@@ -0,0 +1,82 @@
@workInProgress
@ngdoc overview
@name angular.formatter
@namespace Namespace for all formats.
@description
# Overview
The formatters are responsible for translating user readable text in an input widget to a
data model stored in an application.
# Writting your own Formatter
Writing your own formatter is easy. Just register a pair of JavaScript functions with
`angular.formatter`. One function for parsing user input text to the stored form,
and one for formatting the stored data to user-visible text.
Here is an example of a "reverse" formatter: The data is stored in uppercase and in
reverse, while it is displayed in lower case and non-reversed. User edits are
automatically parsed into the internal form and data changes are automatically
formatted to the viewed form.
<pre>
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</pre>
@example
<doc:example>
<doc:source>
<script type="text/javascript">
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</script>
Formatted:
<input type="text" name="data" value="angular" ng:format="reverse"/>
<br/>
Stored:
<input type="text" name="data"/><br/>
<pre>{{data}}</pre>
</doc:source>
<doc:scenario>
it('should store reverse', function(){
expect(element('.doc-example 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');
});
</doc:scenario>
</doc:example>
+66
View File
@@ -0,0 +1,66 @@
@workInProgress
@ngdoc overview
@name angular.markup
@description
#Overview
Markups allow the angular compiler to transform content of DOM elements or portions of this content
into other text or DOM elements for further compilation. Markup extensions do not themselves produce
linking functions. Think of markup as a way to produce shorthand for a {@link angular.widget widget}
or a {@link angular.directive directive}.
#`{{}}` (double curly) built-in markup
`{{}}` markup is a built-in markup, which translates the enclosed expression into an
{@link angular.directive.ng:bind ng:bind} directive. It simply transforms
<pre>
{{expression}}
</pre>
to:
<pre>
<span ng:bind="expression"></span>
</pre>
For example `{{1+2}}` is easier to write and understand than `<span ng:bind="1+2"></span>`. The
expanded elements are then {@link guide.compiler compiled} normally.
# Custom markup
Let's say you want to define this shorthand for a horizontal rule: `---` for `<hr/>`.
In other words, this HTML:
<pre>
header
---
footer
</pre>
should translate to:
<pre>
header
<hr/>
footer
</pre>
Here's how the angular compiler could be extended to achieve this:
<pre>
angular.markup('---', function(text, textNode, parentElement) {
var compiler = this;
var index = text.indexOf('---');
if (index > -1) {
var before = compiler.text(text.substring(0, index));
var hr = compiler.element('hr');
var after = compiler.text(text.substring(index + 3));
textNode.after(after);
textNode.after(hr);
textNode.after(before);
textNode.remove();
}
});
</pre>
Unlike {@link angular.widget widgets} and {@link angular.directive directives}, in which the
compiler matches the name of handler function to a DOM element or attribute name, for markup the
compiler calls every markup handler for every text node, giving the handler a chance to transform
the text. The markup handler needs to find all the matches in the text.
+4
View File
@@ -0,0 +1,4 @@
@workInProgress
@ngdoc overview
@name angular
@namespace The exported angular namespace.
+175
View File
@@ -0,0 +1,175 @@
@workInProgress
@ngdoc overview
@name angular.service
@description
# Overview
Services are substituable objects, which are wired together using dependency injection (DI).
Each service could have dependencies (other services), which are passed in constructor.
Because JS is dynamicaly typed language, dependency injection can not use static types
to identify these dependencies, so each service must explicitely define its dependencies.
This is done by `$inject` property.
# Built-in services
angular provides a set of services for common operations. These services can be overriden by custom
services if needed.
Like other core angular variables and identifiers, the built-in services always start with `$`.
* {@link angular.service.$browser $browser}
* {@link angular.service.$window $window}
* {@link angular.service.$document $document}
* {@link angular.service.$location $location}
* {@link angular.service.$log $log}
* {@link angular.service.$exceptionHandler $exceptionHandler}
* {@link angular.service.$hover $hover}
* {@link angular.service.$invalidWidgets $invalidWidgets}
* {@link angular.service.$route $route}
* {@link angular.service.$xhr $xhr}
* {@link angular.service.$xhr.error $xhr.error}
* {@link angular.service.$xhr.bulk $xhr.bulk}
* {@link angular.service.$xhr.cache $xhr.cache}
* {@link angular.service.$resource $resource}
* {@link angular.service.$cookies $cookies}
* {@link angular.service.$cookieStore $cookieStore}
# Writing your own custom services
angular provides only set of basic services, so for any nontrivial application it will be necessary
to write one or more custom services. To do so, a factory function that creates a services needs to
be registered with angular's dependency injector. This factory function must return an object - the
service (it is not called with the `new` operator).
**angular.service** accepts three parameters:
- `{string} name` - Name of the service.
- `{function()} factory` - Factory function (called just once by DI).
- `{Object} config` - Configuration object with following properties:
- `$inject` - {Array.<string>} - Array of service ids that this service depends on. These
services will be passed as arguments into the factory function in the same order as specified
in the `$inject` array. Defaults to `[]`.
- `$eager` - {boolean} - If true, the service factory will be called and thus, the service will
be instantiated when angular boots. If false, service will be lazily instantiated when it is
first requested during instantiation of a dependant. Defaults to `false`.
The `this` of the factory function is bound to the root scope of the angular application.
angular enables services to participate in dependency injection (DI) by registering themselves with
angular's DI system (injector) under a `name` (id) as well as by declaring dependencies which need
to be provided for the factory function of the registered service. The ability to swap dependencies
for mocks/stubs/dummies in tests allows for services to be highly testable.
Here is an example of very simple service. This service requires $window service (it's
passed as a parameter to factory function) and it's just a function.
This service simple stores all notifications and after third one, it displays all of them by
window alert.
<pre>
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
</pre>
And here is a unit test for this service. We use Jasmine spy (mock) instead of real browser's alert.
<pre>
var mock, notify;
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
notify = angular.service('notify')(mock);
});
it('should not alert first two notifications', function() {
notify('one');
notify('two');
expect(mock.alert).not.toHaveBeenCalled();
});
it('should alert all after third notification', function() {
notify('one');
notify('two');
notify('three');
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
});
it('should clear messages after alert', function() {
notify('one');
notify('two');
notify('third');
notify('more');
notify('two');
notify('third');
expect(mock.alert.callCount).toEqual(2);
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
});
</pre>
# Injecting services into controllers
Using services as dependencies for controllers is very similar to using them as dependencies for
another service.
JavaScript is dynamic language, so DI is not able to figure out which services to inject by
static types (like in static typed languages). Therefore you must specify the service name
by the `$inject` property - it's an array that contains strings with names of services to be
injected. The name must match the id that service has been registered as with angular.
The order of the services in the array matters, because this order will be used when calling
the factory function with injected parameters. The names of parameters in factory function
don't matter, but by convention they match the service ids.
<pre>
function myController($loc, $log) {
this.firstMethod = function() {
// use $location service
$loc.setHash();
};
this.secondMethod = function() {
// use $log service
$log.info('...');
};
}
// which services to inject ?
myController.$inject = ['$location', '$log'];
</pre>
@example
<doc:example>
<doc:source>
<script type="text/javascript">
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
function myController(notifyService) {
this.callNotify = function(msg) {
notifyService(msg);
};
}
myController.$inject = ['notify'];
</script>
<div ng:controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng:init="message='test'" type="text" name="message" />
<button ng:click="callNotify(message);">NOTIFY</button>
</div>
</doc:source>
<doc:scenario>
it('should test service', function(){
expect(element(':input[name=message]').val()).toEqual('test');
});
</doc:scenario>
</doc:example>
+77
View File
@@ -0,0 +1,77 @@
@workInProgress
@ngdoc overview
@name angular.validator
@namespace Namespace for all filters.
@description
# Overview
Validators are a standard way to check the user input against a specific criteria. For
example, you might need to check that an input field contains a well-formed phone number.
# Syntax
Attach a validator on user input widgets using the `ng:validate` attribute.
<doc:example>
<doc:source>
Change me: <input type="text" name="number" ng:validate="integer" value="123">
</doc:source>
<doc:scenario>
it('should validate the default number string', function() {
expect(element('input[name=number]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate "foo"', function() {
input('number').enter('foo');
expect(element('input[name=number]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
# Writing your own Validators
Writing your own validator is easy. To make a function available as a
validator, just define the JavaScript function on the `angular.validator`
object. <angular/> passes in the input to validate as the first argument
to your function. Any additional validator arguments are passed in as
additional arguments to your function.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
the DOM in addition to transforming the input.
In this example we have written a upsTrackingNo validator.
It marks the input text "valid" only when the user enters a well-formed
UPS tracking number.
@css ng-validation-error
When validation fails, this css class is applied to the binding, making its borders red by
default.
@example
<doc:example>
<doc:source>
<script>
angular.validator('upsTrackingNo', function(input, format) {
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
return input.match(regexp)?"":"The format must match " + format;
});
</script>
<input type="text" name="trackNo" size="40"
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
value="1Z 123 456 78 9012 345 6"/>
</doc:source>
<doc:scenario>
it('should validate correct UPS tracking number', function() {
expect(element('input[name=trackNo]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate in correct UPS tracking number', function() {
input('trackNo').enter('foo');
expect(element('input[name=trackNo]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
+78
View File
@@ -0,0 +1,78 @@
@workInProgress
@ngdoc overview
@name angular.widget
@namespace Namespace for all widgets.
@description
# Overview
Widgets allow you to create DOM elements that the browser doesn't
already understand. You create the widget in your namespace and
assign it behavior. You can only bind one widget per DOM element
(unlike directives, in which you can use any number per DOM
element). Widgets are expected to manipulate the DOM tree by
adding new elements whereas directives are expected to only modify
element properties.
Widgets come in two flavors: element and attribute.
# Element Widget
Let's say we would like to create a new element type in the
namespace `my` that can watch an expression and alert() the user
with each new value.
<pre>
&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
<doc:example>
<doc:source>
<script>
angular.widget('my:time', function(compileElement){
compileElement.css('display', 'block');
return function(linkElement){
function update(){
linkElement.text('Current time is: ' + new Date());
setTimeout(update, 1000);
}
update();
};
});
</script>
<my:time></my:time>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
+63
View File
@@ -0,0 +1,63 @@
@workInProgress
@ngdoc overview
@name Cookbook: Resources - Buzz
@description
External resources are URLs that provide JSON data, which are then rendered with the help of
templates. angular has a resource factory that can be used to give names to the URLs and then
attach behavior to them. For example you can use the
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz API}
to retrieve Buzz activity and comments.
<doc:example>
<doc:source>
<script>
BuzzController.$inject = ['$resource'];
function BuzzController($resource){
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt:'json', callback:'JSON_CALLBACK'},
{ get: {method:'JSON', params:{visibility:'@self'}},
replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}
});
}
BuzzController.prototype = {
fetch: function(){
this.activities = this.Activity.get({userId:this.userId});
},
expandReplies: function(activity) {
activity.replies =
this.Activity.replies({userId:this.userId, activityId:activity.id});
}
};
</script>
<div ng:controller="BuzzController">
<input name="userId" value="googlebuzz"/>
<button ng:click="fetch()">fetch</button>
<hr/>
<div class="buzz" ng:repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
<a href="" ng:click="expandReplies(item)" style="float: right;">
Expand replies: {{item.links.replies[0].count}}
</a>
</h1>
{{item.object.content | html}}
<div class="reply" ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
{{reply.content | html}}
</div>
</div>
</div>
</doc:source>
<doc:scenario>
it('fetch buzz and expand', function(){
element(':button:contains(fetch)').click();
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
element('.buzz a:contains(Expand replies):first').click();
expect(repeater('div.reply').count()).toBeGreaterThan(0);
});
</doc:scenario>
</doc:example>
+114
View File
@@ -0,0 +1,114 @@
@workInProgress
@ngdoc overview
@name Cookbook: Deep Linking
@description
Deep linking allows you to encode the state of the application in the URL so that it can be
bookmarked and the application can be restored from the URL to the same state.
While <angular/> does not force you to deal with bookmarks in any particular way, it has services
which make the common case described here very easy to implement.
# Assumptions
Your application consists of a single HTML page which bootstraps the application. We will refer
to this page as the chrome.
Your application is divided into several screens (or views) which the user can visit. For example,
the home screen, settings screen, details screen, etc. For each of these screens, we would like to
assign a URL so that it can be bookmarked and later restored. Each of these screens will be
associated with a controller which define the screen's behavior. The most common case is that the
screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
have multiple partials, but a single partial is the most common construct. This example makes the
partial boundary visible using a blue line.
You can make a routing table which shows which URL maps to which partial view template and which
controller.
# Example
In this example we have a simple app which consist of two screens:
* Welcome: url `#` Show the user contact information.
* Settings: url `#/settings` Show an edit screen for user contact information.
The two partials are defined in the following URLs:
* {@link ./static/settings.html}
* {@link ./static/welcome.html}
<doc:example>
<doc:source>
<script>
AppCntl.$inject = ['$route']
function AppCntl($route) {
// define routes
$route.when("", {template:'./static/welcome.html', controller:WelcomeCntl});
$route.when("/settings", {template:'./static/settings.html', controller:SettingsCntl});
$route.parent(this);
// initialize the model to something useful
this.person = {
name:'anonymous',
contacts:[{type:'email', url:'anonymous@example.com'}]
};
}
function WelcomeCntl($route){}
WelcomeCntl.prototype = {
greet: function(){
alert("Hello " + this.person.name);
}
};
function SettingsCntl(){
this.cancel();
}
SettingsCntl.prototype = {
cancel: function(){
this.form = angular.copy(this.person);
},
save: function(){
angular.copy(this.form, this.person);
window.location.hash = "#";
}
};
</script>
<div ng:controller="AppCntl">
<h1>Your App Chrome</h1>
[ <a href="#">Welcome</a> | <a href="#/settings">Settings</a> ]
<hr/>
<span style="background-color: blue; color: white; padding: 3px;">
Partial: {{$route.current.template}}
</span>
<ng:view style="border: 1px solid blue; margin: 0; display:block; padding:1em;"></ng:view>
<small>Your app footer </small>
</div>
</doc:source>
<doc:scenario>
it('should navigate to URL', function(){
element('a:contains(Welcome)').click();
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
element('a:contains(Settings)').click();
input('form.name').enter('yourname');
element(':button:contains(Save)').click();
element('a:contains(Welcome)').click();
expect(element('ng\\:view').text()).toMatch(/Hello yourname/);
});
</doc:scenario>
</doc:example>
# Things to notice
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
initialization of the {@link angular.service.$rouet $route} service with the proper URL routes.
* The {@link angular.service.$route $route} service then watches the URL and instantiates the
appropriate controller when the URL changes.
* The {@link angular.widget.ng:view ng:view} widget loads the view when the URL changes. It also
sets the view scope to the newly instantiated controller.
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
the URL is changed programatically or by the user.
+103
View File
@@ -0,0 +1,103 @@
@workInProgress
@ngdoc overview
@name Cookbook: Form
@description
A web application's main purpose is to present and gather data. For this reason angular strives
to make both of these operations trivial. This example shows off how you can build a simple form to
allow a user to enter data.
<doc:example>
<doc:source>
<script>
function FormController(){
this.user = {
name: 'John Smith',
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
contacts:[{type:'phone', value:'1(234) 555-1212'}]
};
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
}
</script>
<div ng:controller="FormController" class="example">
<label>Name:</label><br/>
<input type="text" name="user.name" ng:required/> <br/><br/>
<label>Address:</label><br/>
<input type="text" name="user.address.line1" size="33" ng:required/> <br/>
<input type="text" name="user.address.city" size="12" ng:required/>,
<input type="text" name="user.address.state" size="2" ng:required ng:validate="regexp:state"/>
<input type="text" name="user.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
<label>Phone:</label>
[ <a href="" ng:click="user.contacts.$add()">add</a> ]
<div ng:repeat="contact in user.contacts">
<select name="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" name="contact.value" ng:required/>
[ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]
</div>
<hr/>
Debug View:
<pre>user={{user}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should show debug', function(){
expect(binding('user')).toMatch(/John Smith/);
});
it('should add contact', function(){
using('.example').element('a:contains(add)').click();
using('.example div:last').input('contact.value').enter('you@example.org');
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
expect(binding('user')).toMatch(/you@example.org/);
});
it('should remove contact', function(){
using('.example').element('a:contains(X)').click();
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
});
it('should validate zip', function(){
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
.not().toMatch(/ng-validation-error/)
using('.example').input('user.address.zip').enter('abc');
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
.toMatch(/ng-validation-error/)
});
it('should validate state', function(){
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
.not().toMatch(/ng-validation-error/)
using('.example').input('user.address.state').enter('XXX');
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
.toMatch(/ng-validation-error/)
});
</doc:scenario>
</doc:example>
# Things to notice
* The user data model is initialized {@link angular.ng:controller controller} and is available in
the {@link angular.scope scope} with the initial data.
* For debugging purposes we have included a debug view of the model to better understand what
is going on.
* The {@link angular.widget.HTML input widgets} simply refer to the model and are auto bound.
* The inputs {@link angular.validator validate}. (Try leaving them blank or entering non digits
in the zip field)
* In your application you can simply read from or write to the model and the form will be updated.
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
reflected in the view.
+101
View File
@@ -0,0 +1,101 @@
@workInProgress
@ngdoc overview
@name Cookbook: Advanced Form
@description
Here we extend the basic form example to include common features such as reverting, dirty state
detection, and preventing invalid form submission.
<doc:example>
<doc:source>
<script>
function UserForm(){
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
this.master = {
name: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
},
contacts:[
{type:'phone', value:'1(234) 555-1212'}
]
};
this.cancel();
}
UserForm.prototype = {
cancel: function(){
this.form = angular.copy(this.master);
},
save: function(){
this.master = this.form;
this.cancel();
}
};
</script>
<div ng:controller="UserForm">
<label>Name:</label><br/>
<input type="text" name="form.name" ng:required/> <br/><br/>
<label>Address:</label><br/>
<input type="text" name="form.address.line1" size="33" ng:required/> <br/>
<input type="text" name="form.address.city" size="12" ng:required/>,
<input type="text" name="form.address.state" size="2" ng:required ng:validate="regexp:state"/>
<input type="text" name="form.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
<label>Phone:</label>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
<div ng:repeat="contact in form.contacts">
<select name="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" name="contact.value" ng:required/>
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<button ng:click="cancel()" disabled="{{master.$equals(form)}}">Cancel</button>
<button ng:click="save()" disabled="{{$invalidWidgets.visible() || master.$equals(form)}}">Save</button>
<hr/>
Debug View:
<pre>form={{form}}
master={{master}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function(){
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function(){
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
element(':button:contains(Cancel)').click();
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
expect(element(':input[name=form.name]').val()).toEqual('John Smith');
});
</doc:scenario>
</doc:example>
#Things to notice
* Cancel & save buttons are only enabled if the form is dirty -- there is something to cancel or
save.
* Save button is only enabled if there are no validation errors on the form.
* Cancel reverts the form changes back to original state.
* Save updates the internal model of the form.
* Debug view shows the two models. One presented to the user form and the other being the pristine
copy master.
+31
View File
@@ -0,0 +1,31 @@
@workInProgress
@ngdoc overview
@name Cookbook: Hello World
@description
<doc:example>
<doc:source>
Your name: <input type="text" name="name" value="World"/>
<hr/>
Hello {{name}}!
</doc:source>
<doc:scenario>
it('should change the binding when user enters text', function(){
expect(binding('name')).toEqual('World');
input('name').enter('angular');
expect(binding('name')).toEqual('angular');
});
</doc:scenario>
</doc:example>
# Things to notice
Take a look through the source and note:
* The script tag that {@link guide.bootstrap bootstraps} the angular environment.
* The text {@link angular.widget.HTML input widget} which is bound to the greeting name text.
* No need for listener registration and event firing on change events.
* The implicit presence of the `name` variable which is in the root {@link angular.scope scope}.
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
* The concept of {@link guide.data-binding data binding}, which reflects any changes to the
input field in the greeting text.
+125
View File
@@ -0,0 +1,125 @@
@workInProgress
@ngdoc overview
@name Cookbook: MVC
@description
MVC allows for a clean an testable separation between the behavior (controller) and the view
(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
view. This makes it very easy for the controller and the view to share the model.
The model is simply the controller's this. This makes it very easy to test the controller in
isolation since one can simply instantiate the controller and test without a view, because there is
no connection between the controller and the view.
<doc:example>
<doc:source>
<script>
function TicTacToeCntl(){
this.cellStyle= {
'height': '20px',
'width': '20px',
'border': '1px solid black',
'text-align': 'center',
'vertical-align': 'middle',
'cursor': 'pointer'
};
this.reset();
this.$watch('$location.hashSearch.board', this.readUrl);
}
TicTacToeCntl.prototype = {
dropPiece: function(row, col) {
if (!this.winner && !this.board[row][col]) {
this.board[row][col] = this.nextMove;
this.nextMove = this.nextMove == 'X' ? 'O' : 'X';
this.setUrl();
}
},
reset: function(){
this.board = [
['', '', ''],
['', '', ''],
['', '', '']
];
this.nextMove = 'X';
this.winner = '';
this.setUrl();
},
grade: function(){
var b = this.board;
this.winner =
row(0) || row(1) || row(2) ||
col(0) || col(1) || col(2) ||
diagonal(-1) || diagonal(1);
function row(r) { return same(b[r][0], b[r][1], b[r][2]);}
function col(c) { return same(b[0][c], b[1][c], b[2][c]);}
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
function same(a, b, c) { return (a==b && b==c) ? a : '';};
},
setUrl: function(){
var rows = [];
angular.forEach(this.board, function(row){
rows.push(row.join(','));
});
this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove;
},
readUrl: function(value) {
if (value) {
value = value.split('/');
this.nextMove = value[1];
angular.forEach(value[0].split(';'), function(row, i){
this.board[i] = row.split(',');
}, this);
this.grade();
} else {
this.reset();
}
}
};
</script>
<h3>Tic-Tac-Toe</h3>
<div ng:controller="TicTacToeCntl">
Next Player: {{nextMove}}
<div class="winner" ng:show="winner">Player {{winner}} has won!</div>
<table class="board">
<tr ng:repeat="row in board" style="height:15px;">
<td ng:repeat="cell in row" ng:style="cellStyle"
ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
</tr>
</table>
<button ng:click="reset()">reset board</button>
</div>
</doc:source>
<doc:scenario>
it('should play a game', function(){
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
expect(binding('nextMove')).toEqual('X');
piece(1, 2);
piece(3, 2);
piece(1, 3);
expect(element('.winner').text()).toEqual('Player X has won!');
});
function piece(row, col) {
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
}
</doc:scenario>
</doc:example>
# Things to notice
* The controller is defined in JavaScript and has no reference to the rendering logic.
* The controller is instantiated by <angular/> and injected into the view.
* The controller can be instantiated in isolation (without a view) and the code will still execute.
This makes it very testable.
* The HTML view is a projection of the model. In the above example, the model is stored in the
board variable.
* All of the controller's properties (such as board and nextMove) are available to the view.
* Changing the model changes the view.
* The view can call any controller function.
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
hash so the browser's back button will undo game steps. See deep-linking. This example calls
{@link angular.Scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
+60
View File
@@ -0,0 +1,60 @@
@workInProgress
@ngdoc overview
@name Cookbook
@description
Welcome to the angular cookbook. Here we will show you typical uses of angular by example.
# Hello World
{@link cookbook.helloworld Hello World}: The simplest possible application that demonstrates the
classic Hello World!
# Basic Form
{@link cookbook.form Basic Form}: Displaying forms to the user for editing is the bread and butter
of web applications. Angular makes forms easy through bidirectional data binding.
# Advanced Form
{@link cookbook.formadvanced Advanced Form}: Taking the form example to the next level and
providing advanced features such as dirty detection, form reverting and submit disabling if
validation errors exist.
# Model View Controller
{@link cookbook.mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
to separate the behavior (JavaScript controller) from the presentation (HTML view). This
separation aids in maintainability and testability of your project.
# Multi-page App and Deep Linking
{@link cookbook.deeplinking Deep Linking}: An AJAX application never navigates away from the
first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
prevent you from emailing links to a particular screen within your application.
Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
allowing you to send links to specific screens in your app.
# Services
{@link angular.service Services}: Services are long lived objects in your applications that are
available across controllers. A collection of useful services are pre-bundled with angular but you
will likely add your own. Services are initialized using dependency injection, which resolves the
order of initialization. This safeguards you from the perils of global state (a common way to
implement long lived objects).
# External Resources
{@link cookbook.buzz Resources}: Web applications must be able to communicate with the external
services to get and update data. Resources are the abstractions of external URLs which are
specially tailored to angular data binding.
+81
View File
@@ -0,0 +1,81 @@
@workInProgress
@ngdoc overview
@name FAQ
@description
#FAQ
### Why is this project called "angular"? Why is the namespace called "ng"?
Because HTML has angular brackets and "ng" sounds like "angular".
### Is <angular/> an HTML5 tag?
No, <angular/> is not an HTML5 tag. angular is an orthogonal project to HTML5; you can use the two
together.
### Is angular a {library, framework, DOM manipulation library, widget library, native plugin}?
No, angular is none of these. You don't call its functions, it does not call your functions,
it does not provide a way to manipulate DOM, but does provide primitives to create UI projections
of your data. There are lots of existing widget libraries which you can integrate with angular.
It is 100% JavaScript, 100% client side and compatible with both desktop and mobile browsers.
### Do I need to worry about security holes in angular?
Like with any technology, angular is not impervious to attack. angular does, however, provide
built-in protection from basic security holes including cross-site scripting and HTML injection
attacks. angular does round-trip escaping on all strings for you.
### Can I download the source, build, and host the angular environment locally?
Yes. See instructions in {@link guide.downloading downloading}.
### Is angular a templating system?
At the highest level, angular does look like a just another templating system. But there is one
important reason why angular templating system is different and makes it very good fit for
application development: bidirectional data binding. The template is compiled on the browser and
the compilation step produces a live view. This means you, the developer, don't need to write
code to constantly sync the view with the model and the model with the view as in other
templating systems.
### What browsers does angular work with?
Webkit-based browsers (Safari, Chrome, iPhone, Android, WebOS, BlackBerry 6), Firefox, IE6 and
above. Note that CSS only works on IE7 and above.
### What's angular's performance like?
angular takes ~300ms to load, render, and compile. In Chrome it uses about 2-5MB of memory. Your
app's performance will vary depending on how many bindings you use.
### How big is the angular bootstrap JS file that I need to include?
The size of the library itself is < 50KB compressed and obfuscated.
### Can I use the open-source Closure Library with angular?
Yes, you can use widgets from the {@link http://code.google.com/closure/library Closure Library}
in angular.
### Does angular use the jQuery library?
Yes, angular uses {@link http://jquery.com/ jQuery}, the open source DOM manipulation library.
If jQuery is not present in your script path, angular falls back on its own implementation of
{@link angular.element jQuery lite}. If jQuery is present in the path, angular uses it to
manipulate the DOM.
### What is testability like in angular?
Very testable. It has an integrated dependency injection framework. See
{@link angular.service service} for details.
### How can I learn more about angular?
Watch the July 28, 2010 talk
"{@link http://www.youtube.com/watch?v=elvcgVSynRg| Angular: A Radically Different Way of Building AJAX Apps}".
### How is angular licensed?
The MIT License.
+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.
+27
View File
@@ -0,0 +1,27 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Contributing
@description
# Open Source
Angular is an open source project licensed under the
{@link http://github.com/angular/angular.js/blob/master/LICENSE MIT license}. We welcome any
contributions from the community, but in order to make the contribution process manageable, we ask
you to follow the following guidelines:
# Source Code
* Discuss any major changes on our {@link http://groups.google.com/group/angular mailing list}.
For small changes and bugfixes, just go ahead and craft your patch and submit it.
* Use coding style described
{@link http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml here}.
* To submit any changes, we ask you to use {@link http://github.com}
* Create a github {@link https://github.com/signup/free account}
* Clone the {@link http://github.com/angular/angular.js main angular repository}
* Create a new branch off of the master (`git branch my-fix-branch`)
* Switch to the branch (`git checkout my-fix-branch`), make your changes and commit (`git commit -a`)
* For any new features or regressions we require a test(s) to be submitted with the change
* Run js lint (`rake lint`) and fix any warnings or errors
* Push your branch to github (`git push origin my-fix-branch`)
* Send a pull request via github into `angular:master`
* Once the patch is reviewed and merged, you can delete your branch (`git push origin :my-fix-branch; git checkout master; git branch -D my-fix-branch`)
* TADA!
+46
View File
@@ -0,0 +1,46 @@
@workInProgress
@ngdoc overview
@name Developer Guide: CSS
@description
# CSS
angular includes built-in CSS classes, which in turn have predefined CSS styles.
# Built-in CSS classes
## `ng-exception`
**Usage:** angular applies this class to a DOM element if that element contains an Expression that
threw an exception when evaluated.
**Styling:** The built-in styling of the ng-exception class displays an error message surrounded
by a solid red border, for example:
> <div class="ng-exception">Error message</div>
You can try to evaluate malformed expressions in {@link angualr.expression expression} to see the
`ng-exception` class' styling.
## `ng-validation-error`
**Usage:** angular applies this class to an input widget element if that element's input does not
pass validation. Note that you set the validation criteria on the input widget element using the
Ng:validate or Ng:required directives.
**Styling:** The built-in styling of the ng-validation-error class turns the border of the input
box red and includes a hovering UI element that includes more details of the validation error. You
can see an example in {@link angular.widget.@ng:validate ng:validate example}.
## How to override the styles for built-in classes
To override the styles for these built-in classes, you can do any of the following:
Download the source code, edit angular.css, and host the source on your own server.
Create a local css file, overriding any styles that you'd like, and link to it from your HTML file
as you normally would:
<pre>
<link href="yourfile.css" rel="stylesheet" type="text/css">
</pre>
+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}.
+35
View File
@@ -0,0 +1,35 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Downloading
@description
# Downloading
Follow these steps to download, compile, and host angular.js on your own server:
## Step 0
If you don't already have Rake (Ruby Make) installed, download and install it from
http://rake.rubyforge.org/.
## Step 1
Download the angular source code from GitHub: http://github.com/angular/angular.js.
## Step 2
The source code should include a Rakefile. Compile the source by typing rake compile.
## Step 3
Host the files on your server. Look through your source tree and make sure that the css and js
directories are parallel (i.e. share a common root node).
In the js directory you should see these different versions of the angular bootstrap code:
* **angular-?.?.?.js** - this file is unobfuscated, uncompressed, and human-readable. Note that
despite the name of the file, there is no additional functionality built in to help you debug
your application; it has the prefix debug because you can read the source code.
* **angular-?.?.?.min.js** - this is a compressed and obfuscated version of `angular-?.?.?.js`.
You might want to use this version if you want to load a smaller but functionally equivalent
version of the code in your application. Note: this minified version was created using the
Closure Compiler.
+207
View File
@@ -0,0 +1,207 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Expression
@description
# Expressions
Expressions are the bindings that you write in HTML and embed in templates in order to create
views in angular. They are not equivalent to JavaScript expressions.
For example, these are all valid expressions in angular:
* `1+2={{1+2}}`
* `3*10|currency`
* `Hello {{name}}!`
* `Hello {{'World'}}!`
# angular expressions vs. JS expressions
It might be tempting to think of angular view expressions as JavaScript expressions, but that is
not entirely correct. angular does not use a simple JavaScript eval of the expression text. You
can think of angular expressions as JavaScript expressions with these differences:
* **Attribute Evaluation:** evaluation of all attributes are against the current scope, not to
the global window as in JavaScript.
* **Forgiving:** expression evaluation is forgiving to undefined and null, unlike in JavaScript.
* **No Control Flow Statements:** you cannot do the following from an angular expression:
conditionals, loops, or throw.
* **Type Augmentation:** the scope expression evaluator augments built-in types.
* **Filters:** you can add filters to an expression, for example to convert raw data into a
human-readable format.
* **The $:** angular reserves this prefix to differentiate its API names from others.
If you want to run arbitrary JavaScript code, make it a controller method and call that. If you
want to eval an angular expression from JavaScript, use the Scope:$eval() method.
## Example
<doc:example>
<doc:source>
1+2={{1+2}}
</doc:source>
<doc:scenario>
it('should calculate expression in binding', function(){
expect(binding('1+2')).toEqual('3');
});
</doc:scenario>
</doc:example>
You can try evaluating different expressions here:
<doc:example>
<doc:source>
<div ng:init="exprs=[]" class="expressions">
Expression:
<input type='text' name="expr" value="3*10|currency" size="80"/>
<button ng:click="exprs.$add(expr)">Evaluate</button>
<ul>
<li ng:repeat="expr in exprs">
[ <a href="" ng:click="exprs.$remove(expr)">X</a> ]
<tt>{{expr}}</tt> => <span ng:bind="$parent.$eval(expr)"></span>
</li>
</ul>
</div>
</doc:source>
<doc:scenario>
it('should allow user expression testing', function(){
element('.expressions :button').click();
var li = using('.expressions ul').repeater('li');
expect(li.count()).toBe(1);
expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
});
</doc:scenario>
</doc:example>
# Attribute Evaluation
Evaluation of all attributes are against the current scope. Unlike JavaScript, where names
default to global window properties, angular expressions have to use $window to refer to the
global object. E.g. if you want to call alert(), which is defined on window, an expression must
use $window.alert(). This is done intentionally to prevent accidental access to the global state
(a common source of subtle bugs).
<doc:example>
<doc:source>
<div class="example2" ng:init="$window = $service('$window')">
Name: <input name="name" type="text" value="World"/>
<button ng:click="($window.mockWindow || $window).alert('Hello ' + name)">Greet</button>
</div>
</doc:source>
<doc:scenario>
it('should calculate expression in binding', function(){
var alertText;
this.addFutureAction('set mock', function($window, $document, done) {
$window.mockWindow = {
alert: function(text){ alertText = text; }
};
done();
});
element(':button:contains(Greet)').click();
expect(this.addFuture('alert text', function(done) {
done(null, alertText);
})).toBe('Hello World');
});
</doc:scenario>
</doc:example>
## Forgiving
Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating a.b.c throws
an exception if a is not an object. While this makes sense for a general purpose language, the
expression evaluations are primarily used for data binding, which often look like this: `{{a.b.c}}`.
It makes more sense to show nothing than to throw an exception if a is undefined (e.g. perhaps
we are waiting for the server response, and it will become defined soon). If expression
evaluation wasn't forgiving we'd have to write bindings that clutter the code, for example:
`{{((a||{}).b||{}).c}}`
Similarly, invoking a function a.b.c() on undefined or null simply returns undefined.
Assignments work the same way in reverse. a.b.c = 10 creates the intermediary objects even if a
is undefined.
## No Control Flow Statements
You cannot write a control flow statement in an expression. The reason behind this is core to
the angular philosophy that application logic should be in controllers, not in the view. If you
need a conditional (including ternary operators), loop, or to throw from a view expression,
delegate to a JavaScript method instead.
## Type Augmentation
Built-in types have methods like [].push(), but the richness of these methods is limited. Consider
the example below, which allows you to do a simple search over a canned set of contacts. The
example would be much more complicated if we did not have the Array:$filter(). There is no
built-in method on Array called $filter and angular doesn't add it to Array.prototype because that
could collide with other JavaScript frameworks.
For this reason the scope expression evaluator augments the built-in types to make them act like
they have extra methods. The actual method for $filter() is angular.Array.filter(). You can call
it from JavaScript.
Extensions: You can further extend the expression vocabulary by adding new methods to
`angular.Array` or `angular.String`, etc.
<doc:example>
<doc:source>
<div ng:init="friends = [
{name:'John', phone:'555-1212'},
{name:'Mary', phone:'555-9876'},
{name:'Mike', phone:'555-4321'},
{name:'Adam', phone:'555-5678'},
{name:'Julie', phone:'555-8765'}]"></div>
Search: <input name="searchText"/>
<table class="example3">
<tr><th>Name</th><th>Phone</th><tr>
<tr ng:repeat="friend in friends.$filter(searchText)">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should filter the list', function(){
var tr = using('table.example3').repeater('tr.ng-attr-widget');
expect(tr.count()).toBe(5);
input('searchText').enter('a');
expect(tr.count()).toBe(2);
});
</doc:scenario>
</doc:example>
## Filters
When presenting data to the user, you might need to convert the data from its raw format to a
user-friendly format. For example, you might have a data object that needs to be formatted
according to the locale before displaying it to the user. You can pass expressions through a
chain of filters like this:
<pre>
name | uppercase
</pre>
The expression evaluator simply passes the value of name to angular.filter.uppercase.
Chain filters using this syntax:
<pre>
value | filter1 | filter2
</pre>
You can also pass colon-delimited arguments to filters, for example, to display the number 123
with 2 decimal points: 123 | number:2
# The $
You might be wondering, what is the significance of the $ prefix? It is simply a prefix that
angular chooses to differentiate its API names from others. If angular didn't use $, then
evaluating a.length() would return undefined because neither a nor angular define such a property.
Consider that in a future version of angular we might choose to add a length method, in which case
the behavior of the expression would change. Worse yet, you the developer could create a length
property and then we would have collision. This problem exists because angular augments existing
objects with additional behavior. By prefixing its additions with $ we are reserving our namespace
so that angular developers and developers who use angular can develop in harmony without
collisions.
+37
View File
@@ -0,0 +1,37 @@
@workInProgress
@ngdoc overview
@name Developer Guide
@description
* {@link guide.overview Overview} - An overview of angular, including its philosophy and how it
works.
* {@link guide.bootstrap Bootstrap} - How to bootstrap your application to the angular environment.
* {@link guide.template Template} - How to define your application's view using HTML, CSS, and
other built-in angular constructs.
* {@link guide.compiler Compiler} - All about the HTML compiler that's at the core of angular.
* {@link angular.directive Directive} - How to use XML attributes to augment an existing DOM
element.
* {@link angular.markup Markup} - How to use markup to create shorthand for a widget or a
directive. For example, markup is what allows you to use the double curly brace notation
`{{}}` to bind expressions to elements.
* {@link guide.data-binding Data Binding} - About the mechanism that keeps the model the single
source of truth of your application at all times, with the view as a live projection of the
model.
* {@link angular.filter Filter} - How to format your data for display to the user.
* {@link angular.widget Widget} - How to create new DOM elements that the browser doesn't already
understand.
* {@link angular.validator Validator} - How to validate user input.
* {@link angular.formatter Formatter} - How to format stored data to user-readable text and
parse the text back to the stored form.
* {@link guide.css CSS} - Built-in CSS classes, when angular assigns them, and how to override
their styles.
* {@link angular.scope Scope} - The model in the model-view-controller design pattern. You can
think about scopes as the JavaScript objects that have extra APIs for registering watchers.
* {@link guide.expression Expression} - The bindings that are embedded in an angular View.
* {@link angular.service Service} - Objects that are wired through dependency injection and then
injected into the root scope.
* {@link guide.testing Testing}
* service:$browser(mock)
* {@link guide.downloading Downloading} - How to download, compile, and host the angular
environment on your own server.
* {@link guide.contribute Contributing} - How to contribute to angular project.
+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.
+8
View File
@@ -0,0 +1,8 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Testing
@description
# Testing Angular Applications
to be written...
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+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}}
+33
View File
@@ -0,0 +1,33 @@
var DOM = require('dom.js').DOM;
describe('dom', function(){
var dom;
beforeEach(function(){
dom = new DOM();
});
describe('h', function(){
it('should render using function', function(){
var cbThis;
var cdValue;
dom.h('heading', 'content', function(value){
cbThis = this;
cbValue = value;
});
expect(cbThis).toEqual(dom);
expect(cbValue).toEqual('content');
});
it('should update heading numbers', function(){
dom.h('heading', function(){
this.html('<h1>sub-heading</h1>');
});
expect(dom.toString()).toContain('<h1>heading</h1>');
expect(dom.toString()).toContain('<h2>sub-heading</h2>');
});
});
});
+449
View File
@@ -0,0 +1,449 @@
var ngdoc = require('ngdoc.js');
var DOM = require('dom.js').DOM;
describe('ngdoc', function(){
var Doc = ngdoc.Doc;
var dom;
beforeEach(function(){
dom = new DOM();
this.addMatchers({
toContain: function(text) {
this.actual = this.actual.toString();
return this.actual.indexOf(text) > -1;
}
});
});
describe('Doc', function(){
describe('metadata', function(){
it('should find keywords', function(){
expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world');
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
});
it('should have shortName', function(){
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng:c');
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
});
it('should have depth information', function(){
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].depth).toEqual(2);
expect(ngdoc.metadata([d2])[0].depth).toEqual(2);
expect(ngdoc.metadata([d3])[0].depth).toEqual(1);
});
});
describe('parse', function(){
it('should convert @names into properties', function(){
var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n');
doc.parse();
expect(doc.name).toEqual('name');
expect(doc.desc).toEqual('desc\ndesc2');
expect(doc.dep).toEqual('');
});
it('should parse parameters', function(){
var doc = new Doc(
'@param {*} a short\n' +
'@param {Type} b med\n' +
'@param {Class=} [c=2] long\nline');
doc.parse();
expect(doc.param).toEqual([
{name:'a', description:'<p>short</p>', type:'*', optional:false, 'default':undefined},
{name:'b', description:'<p>med</p>', type:'Type', optional:false, 'default':undefined},
{name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'}
]);
});
it('should parse return', function(){
var doc = new Doc('@returns {Type} text *bold*.');
doc.parse();
expect(doc.returns).toEqual({
type: 'Type',
description: '<p>text <em>bold</em>.</p>'
});
});
it('should parse filename', function(){
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
doc.parse(0);
expect(doc.id).toEqual('a.b');
expect(doc.name).toEqual('friendly name');
});
it('should escape <doc:source> element', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:source>\n<>\n</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<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('markdown', function(){
it('should replace angular in markdown', function(){
expect(new Doc().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(new Doc().markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
toEqual(
'<p>bah x</p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
'&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(new Doc().markdown('<pre>x</pre># One<pre>b</pre>')).
toMatch('</div><h1>One</h1><div');
});
it('should unindent text before processing based on the second line', function() {
expect(new Doc().markdown('first line\n' +
' second line\n\n' +
' third line\n' +
' fourth line\n\n' +
' fifth line')).
toMatch('<p>first line\n' +
'second line</p>\n\n' +
'<pre><code>third line\n' +
' fourth line\n</code></pre>\n\n' +
'<p>fifth line</p>');
});
it('should unindent text before processing based on the first line', function() {
expect(new Doc().markdown(' first line\n\n' +
' second line\n' +
' third line\n' +
' fourth line\n\n' +
' fifth line')).
toMatch('<p>first line</p>\n\n' +
'<pre><code>second line\n' +
'third line\n' +
' fourth line\n</code></pre>\n\n' +
'<p>fifth line</p>');
});
});
describe('trim', function(){
var trim = ngdoc.trim;
it('should remove leading/trailing space', function(){
expect(trim(' \nabc\n ')).toEqual('abc');
});
it('should remove leading space on every line', function(){
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
});
});
describe('merge', function(){
it('should merge child with parent', function(){
var parent = new Doc({name:'angular.service.abc'});
var methodA = new Doc({name:'methodA', methodOf:'angular.service.abc'});
var methodB = new Doc({name:'methodB', methodOf:'angular.service.abc'});
var propA = new Doc({name:'propA', propertyOf:'angular.service.abc'});
var propB = new Doc({name:'propB', propertyOf:'angular.service.abc'});
var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order;
ngdoc.merge(docs);
expect(docs.length).toEqual(1);
expect(docs[0].name).toEqual('angular.service.abc');
expect(docs[0].methods).toEqual([methodA, methodB]);
expect(docs[0].properties).toEqual([propA, propB]);
});
});
////////////////////////////////////////
describe('TAG', function(){
describe('@param', function(){
it('should parse with no default', function(){
var doc = new Doc('@param {(number|string)} number Number \n to format.');
doc.parse();
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'number',
optional: false,
'default' : undefined,
description : '<p>Number \nto format.</p>' }]);
});
it('should parse with default and optional', function(){
var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc');
doc.parse();
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'fractionSize',
optional: true,
'default' : '2',
description : '<p>desc</p>' }]);
});
});
describe('@requires', function() {
it('should parse more @requires tag into array', function() {
var doc = new Doc('@requires $service\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 not parse @property without a type', function() {
var doc = new Doc("@property fake");
expect(function() { doc.parse(); }).
toThrow(new Error("Not a valid 'property' format: fake"));
});
it('should parse @property with type', function() {
var doc = new Doc("@property {string} name");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].type).toEqual('string');
});
it('should parse @property with optional description', function() {
var doc = new Doc("@property {string} name desc rip tion");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
});
it('should parse @property with type and description both', function() {
var doc = new Doc("@property {bool} name desc rip tion");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].type).toEqual('bool');
expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
});
});
describe('@returns', function() {
it('should not parse @returns without type', function() {
var doc = new Doc("@returns lala");
expect(doc.parse).toThrow();
});
it('should parse @returns with type and description', function() {
var doc = new Doc("@returns {string} descrip tion");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip tion</p>'});
});
it('should transform description of @returns with markdown', function() {
var doc = new Doc("@returns {string} descrip *tion*");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip <em>tion</em></p>'});
});
it('should support multiline content', function() {
var doc = new Doc("@returns {string} description\n new line\n another line");
doc.parse();
expect(doc.returns).
toEqual({type: 'string', description: '<p>description\nnew line\nanother line</p>'});
});
});
describe('@description', function(){
it('should support pre blocks', function(){
var doc = new Doc("@description <pre>abc</pre>");
doc.parse();
expect(doc.description).
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>');
});
it('should support multiple pre blocks', function() {
var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
doc.parse();
expect(doc.description).
toBe('<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
'<h1>bah</h1>\n\n' +
'<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
});
it('should support nested @link annotations with or without description', function() {
var doc = new Doc("@description " +
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
'dad{@link angular.foo}\n\n' +
'external{@link http://angularjs.org}\n\n' +
'external{@link ./static.html}\n\n' +
'{@link angular.directive.ng:foo ng:foo}');
doc.parse();
expect(doc.description).
toContain('foo <a href="#!angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
toContain('da <a href="#!angular.foo"><code>bar foo bar</code></a>');
expect(doc.description).
toContain('dad<a href="#!angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
toContain('<a href="#!angular.directive.ng:foo"><code>ng:foo</code></a>');
expect(doc.description).
toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
expect(doc.description).
toContain('<a href="./static.html">./static.html</a>');
});
});
describe('@example', function(){
it('should not remove {{}}', function(){
var doc = new Doc('@example text {{ abc }}');
doc.parse();
expect(doc.example).toEqual('<p>text {{ abc }}</p>');
});
it('should support doc:example', function(){
var doc = new Doc('@ngdoc overview\n@example \n' +
'<doc:example>\n' +
' <doc:source><escapeme></doc:source>\n' +
' <doc:scenario><scenario></doc:scenario>\n' +
'</doc:example>').parse();
var html = doc.html();
expect(html).toContain('<doc:source>&lt;escapeme&gt;</doc:source>');
expect(html).toContain('<doc:scenario>&lt;scenario&gt;</doc:scenario>');
expect(doc.scenarios).toEqual(['<scenario>']);
});
});
describe('@depricated', function() {
it('should parse @depricated', function() {
var doc = new Doc('@deprecated Replaced with foo.');
doc.parse();
expect(doc.deprecated).toBe('Replaced with foo.');
});
});
describe('@this', function(){
it('should render @this', function() {
var doc = new Doc('@this I am self.');
doc.ngdoc = 'filter';
doc.parse();
expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
'<div>' +
'<p>I am self.</p>' +
'</div>\n');
expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
'<div><p>I am self.</p></div>');
});
});
});
describe('usage', function(){
describe('overview', function(){
it('should supress description heading', function(){
var doc = new Doc('@ngdoc overview\n@name angular\n@description\n#heading\ntext');
doc.parse();
expect(doc.html()).toContain('text');
expect(doc.html()).toContain('<h2>heading</h2>');
expect(doc.html()).not.toContain('Description');
});
});
describe('filter', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'formatter',
shortName:'myFilter',
param: [
{name:'a'},
{name:'b'}
]
});
doc.html_usage_filter(dom);
expect(dom).toContain('myFilter_expression | myFilter:b');
expect(dom).toContain('angular.filter.myFilter(a, b)');
});
});
describe('validator', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'validator',
shortName:'myValidator',
param: [
{name:'a'},
{name:'b'}
]
});
doc.html_usage_validator(dom);
expect(dom).toContain('ng:validate="myValidator:b"');
expect(dom).toContain('angular.validator.myValidator(a, b)');
});
});
describe('formatter', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'formatter',
shortName:'myFormatter',
param: [
{name:'a'},
]
});
doc.html_usage_formatter(dom);
expect(dom).toContain('ng:format="myFormatter:a"');
expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);');
expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);');
});
});
});
});
+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"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
'</urlset>', ''].join('\n'));
});
it('should render ngdoc url', function(){
var map = new SiteMap([new Doc({name: 'a.b.c<>\'"&'})]);
expect(map.render()).toContain([
' <url>',
'<loc>http://docs.angularjs.org/#!a.b.c&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{}');
});
});
});
+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('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
docs.forEach(function(doc){
map.push(' <url><loc>http://docs.angularjs.org/#!' +
encode(doc.name) + '</loc><changefreq>weekly</changefreq></url>');
});
map.push('</urlset>');
map.push('');
return map.join('\n');
};
function encode(text){
return text
.replace(/&/mg, '&amp;')
.replace(/</mg, '&lt;')
.replace(/>/mg, '&gt;')
.replace(/'/mg, '&apos;')
.replace(/"/mg, '&quot;');
}
}
+69
View File
@@ -0,0 +1,69 @@
function noop(){}
function chain(delegateFn, explicitDone){
var onDoneFn = noop;
var onErrorFn = function(e){
console.error(e.stack || e);
process.exit(-1);
};
var waitForCount = 1;
delegateFn = delegateFn || noop;
var stackError = new Error('capture stack');
function decrementWaitFor() {
waitForCount--;
if (waitForCount == 0)
onDoneFn();
}
function self(){
try {
return delegateFn.apply(self, arguments);
} catch (error) {
self.error(error);
} finally {
if (!explicitDone)
decrementWaitFor();
}
};
self.onDone = function(callback){
onDoneFn = callback;
return self;
};
self.onError = function(callback){
onErrorFn = callback;
return self;
};
self.waitFor = function(callback){
if (waitForCount == 0)
throw new Error("Can not wait on already called callback.");
waitForCount++;
return chain(callback).onDone(decrementWaitFor).onError(self.error);
};
self.waitMany = function(callback){
if (waitForCount == 0)
throw new Error("Can not wait on already called callback.");
waitForCount++;
return chain(callback, true).onDone(decrementWaitFor).onError(self.error);
};
self.done = function(callback){
decrementWaitFor();
};
self.error = function(error) {
var stack = stackError.stack.split(/\n\r?/).splice(2);
var nakedStack = [];
stack.forEach(function(frame){
if (!frame.match(/callback\.js:\d+:\d+\)$/))
nakedStack.push(frame);
});
error.stack = error.stack + '\nCalled from:\n' + nakedStack.join('\n');
onErrorFn(error);
};
return self;
}
exports.chain = chain;
+126
View File
@@ -0,0 +1,126 @@
/**
* 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);
});
},
h: function(heading, content, fn){
if (content==undefined || (content instanceof Array && content.length == 0)) return;
this.headingDepth++;
this.tag('h' + this.headingDepth, heading);
var className = typeof heading == 'string'
? {'class': heading.toLowerCase().replace(/[^\d\w_]/mg, '-').replace(/-+/gm, '-')}
: null;
if (content instanceof Array) {
this.ul(content, className, fn);
} else if (fn) {
this.tag('div', className, function(){
fn.call(this, content);
});
} else {
this.tag('div', className, content);
}
this.headingDepth--;
},
h1: function(attr, text) {
this.tag('h1', attr, text);
},
h2: function(attr, text) {
this.tag('h2', attr, text);
},
h3: function(attr, text) {
this.tag('h3', attr, text);
},
p: function(attr, text) {
this.tag('p', attr, text);
},
ul: function(list, attr, fn) {
if (typeof attr == 'function') {
fn = attr;
attr = {};
}
this.tag('ul', attr, function(dom){
list.forEach(function(item){
dom.out.push('<li>');
dom.text(fn ? fn(item) : item);
dom.out.push('</li>\n');
});
});
}
};
+53
View File
@@ -0,0 +1,53 @@
require.paths.push(__dirname);
require.paths.push('lib');
var reader = require('reader.js'),
ngdoc = require('ngdoc.js'),
writer = require('writer.js'),
callback = require('callback.js'),
SiteMap = require('SiteMap.js').SiteMap;
var docs = [];
var start;
var work = callback.chain(function(){
start = now();
console.log('Generating Angular Reference Documentation...');
reader.collect(work.waitMany(function(text, file, line){
var doc = new ngdoc.Doc(text, file, line);
docs.push(doc);
doc.parse();
}));
});
var writes = callback.chain(function(){
ngdoc.merge(docs);
docs.forEach(function(doc){
writer.output(doc.id + '.html', doc.html(), writes.waitFor());
});
var metadata = ngdoc.metadata(docs);
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';'], writes.waitFor());
writer.copyDir('img', writes.waitFor());
writer.copyDir('static', writes.waitFor());
writer.copy('index.html', writes.waitFor());
writer.copy('docs.js', writes.waitFor());
writer.copy('docs.css', writes.waitFor());
writer.copy('doc_widgets.js', writes.waitFor());
writer.copy('doc_widgets.css', writes.waitFor());
writer.copy('docs-scenario.html', writes.waitFor());
writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
writer.output('sitemap.xml', new SiteMap(docs).render(), writes.waitFor());
writer.output('robots.txt', 'Sitemap: http://docs.angularjs.org/sitemap.xml\n', writes.waitFor());
writer.copy('syntaxhighlighter/shBrushJScript.js', writes.waitFor());
writer.copy('syntaxhighlighter/shBrushXml.js', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.css', writes.waitFor());
writer.copy('syntaxhighlighter/shCore.js', writes.waitFor());
writer.copy('syntaxhighlighter/shThemeDefault.css', writes.waitFor());
writer.copy('jquery.min.js', writes.waitFor());
});
writes.onDone(function(){
console.log('DONE. Generated ' + docs.length + ' pages in ' +
(now()-start) + 'ms.' );
});
work.onDone(writes);
writer.makeDir('build/docs/syntaxhighlighter', work);
///////////////////////////////////
function now(){ return new Date().getTime(); }
View File
+663
View File
@@ -0,0 +1,663 @@
/**
* All parsing/transformation code goes here. All code here should be sync to ease testing.
*/
var Showdown = require('showdown').Showdown;
var DOM = require('dom.js').DOM;
var htmlEscape = require('dom.js').htmlEscape;
var NEW_LINE = /\n\r?/;
exports.trim = trim;
exports.metadata = metadata;
exports.scenarios = scenarios;
exports.merge = merge;
exports.Doc = Doc;
//////////////////////////////////////////////////////////
function Doc(text, file, line) {
if (typeof text == 'object') {
for ( var key in text) {
this[key] = text[key];
}
} else {
this.text = text;
this.file = file;
this.line = line;
}
this.scenarios = this.scenarios || [];
this.requires = this.requires || [];
this.param = this.param || [];
this.properties = this.properties || [];
this.methods = this.methods || [];
}
Doc.METADATA_IGNORE = (function(){
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
return words.toString().split(/[,\s\n\r]+/gm);
})();
Doc.prototype = {
keywords: function keywords(){
var keywords = {};
Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
var words = [];
var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
tokens.forEach(function(key){
var match = key.match(/^(([\$\_a-z]|ng\:)[\w\_\-]{2,})/);
if (match){
key = match[1];
if (!keywords[key]) {
keywords[key] = true;
words.push(key);
}
}
});
words.sort();
return words.join(' ');
},
markdown: function (text) {
var self = this;
var IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/;
var IS_ANGULAR = /^angular\./;
if (!text) return text;
text = trim(text);
var parts = text.split(/(<pre>[\s\S]*?<\/pre>|<doc:example>[\s\S]*?<\/doc:example>)/);
parts.forEach(function(text, i){
if (text.match(/^<pre>/)) {
text = text.replace(/^<pre>([\s\S]*)<\/pre>/mi, function(_, content){
return '<div ng:non-bindable><pre class="brush: js; html-script: true;">' +
content.replace(/</g, '&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){
self.scenarios.push(content);
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.match(IS_URL) ? '' : '#!') + url + '">'
+ (url.match(IS_ANGULAR) ? '<code>' : '')
+ (title || url)
+ (url.match(IS_ANGULAR) ? '</code>' : '')
+ '</a>';
});
text = new Showdown.converter().makeHtml(text);
}
parts[i] = text;
});
return parts.join('');
},
parse: function(){
var atName;
var atText;
var match;
var self = this;
self.text.split(NEW_LINE).forEach(function(line){
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
// we found @name ...
// if we have existing name
flush();
atName = match[1];
atText = [];
if(match[3]) atText.push(match[3]);
} else {
if (atName) {
atText.push(line);
}
}
});
flush();
this.shortName = (this.name || '').split(/[\.#]/).pop();
this.id = this.id // if we have an id just use it
|| (((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // try to extract it from file name
|| this.name; // default to name
this.description = this.markdown(this.description);
this.example = this.markdown(this.example);
this['this'] = this.markdown(this['this']);
return this;
function flush(){
if (atName) {
var text = trim(atText.join('\n'));
if (atName == 'param') {
var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 12 2 34 4 5 5 6 6 3 7 7
if (!match) {
throw new Error("Not a valid 'param' format: " + text);
}
var param = {
name: match[5] || match[4],
description:self.markdown(text.replace(match[0], match[7])),
type: match[1],
optional: !!match[2],
'default':match[6]
};
self.param.push(param);
} else if (atName == 'returns') {
var match = text.match(/^{([^}=]+)}\s+(.*)/);
if (!match) {
throw new Error("Not a valid 'returns' format: " + text);
}
self.returns = {
type: match[1],
description: self.markdown(text.replace(match[0], match[2]))
};
} else if(atName == 'requires') {
self.requires.push(text);
} else if(atName == 'property') {
var match = text.match(/^{(\S+)}\s+(\S+)(\s+(.*))?/);
if (!match) {
throw new Error("Not a valid 'property' format: " + text);
}
var property = {
type: match[1],
name: match[2],
description: self.markdown(text.replace(match[0], match[4]))
};
self.properties.push(property);
} else {
self[atName] = text;
}
}
}
},
html: function(){
var dom = new DOM(),
self = this;
dom.h(this.name, function(){
notice('workInProgress', 'Work in Progress',
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
notice('deprecated', 'Deprecated API', self.deprecated);
(self['html_usage_' + self.ngdoc] || function(){
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom);
dom.h('Example', self.example, dom.html);
});
return dom.toString();
//////////////////////////
function notice(name, legend, msg){
if (self[name] == undefined) return;
dom.tag('fieldset', {'class':name}, function(dom){
dom.tag('legend', legend);
dom.text(msg);
});
}
},
html_usage_parameters: function(dom) {
dom.h('Parameters', this.param, function(param){
dom.tag('code', function(){
dom.text(param.name);
if (param.optional) {
dom.tag('i', function(){
dom.text('(optional');
if(param['default']) {
dom.text('=' + param['default']);
}
dom.text(')');
});
}
dom.text(' {');
dom.text(param.type);
dom.text('} ');
});
dom.html(param.description);
});
},
html_usage_returns: function(dom) {
var self = this;
if (self.returns) {
dom.h('Returns', function(){
dom.tag('code', '{' + self.returns.type + '}');
dom.text(' ');
dom.html(self.returns.description);
});
}
},
html_usage_this: function(dom) {
var self = this;
if (self['this']) {
dom.h(function(dom){
dom.html("Method's <code>this</code>");
}, function(dom){
dom.html(self['this']);
});
}
},
html_usage_function: function(dom){
var self = this;
dom.h('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){
var self = this;
dom.h('Description', this.description, dom.html);
dom.h('Dependencies', this.requires);
if (this.param.length) {
dom.h('Usage', function(){
dom.code(function(){
dom.text(self.name.split('.').pop());
dom.text('(');
self.parameters(dom, ', ');
dom.text(');');
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
}
dom.h('Methods', this.methods, function(method){
var signature = (method.param || []).map(property('name'));
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description);
method.html_usage_parameters(dom);
dom.h('Example', method.example, dom.html);
});
});
dom.h('Properties', this.properties, function(property){
dom.h(property.name, function(){
dom.html(property.description);
dom.h('Example', property.example, dom.html);
});
});
},
parameters: function(dom, separator, skipFirst, prefix) {
var sep = prefix ? separator : '';
(this.param||[]).forEach(function(param, i){
if (!(skipFirst && i==0)) {
if (param.optional) {
dom.text('[' + sep + param.name + ']');
} else {
dom.text(sep + param.name);
}
}
sep = separator;
});
}
};
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
function scenarios(docs){
var specs = [];
docs.forEach(function(doc){
specs.push('describe("' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
specs.push(' browser().navigateTo("index.html#!' + doc.id + '");');
specs.push(' });');
specs.push('');
doc.scenarios.forEach(function(scenario){
specs.push(indent(trim(scenario), 2));
specs.push('');
});
specs.push('});');
specs.push('');
if (doc.scenario) {
}
});
return specs.join('\n');
}
//////////////////////////////////////////////////////////
function metadata(docs){
var words = [];
docs.forEach(function(doc){
var path = (doc.name || '').split(/(\.|\:\s+)/);
for ( var i = 1; i < path.length; i++) {
path.splice(i, 1);
}
var depth = path.length - 1;
var shortName = path.pop();
words.push({
id: doc.id,
name: doc.name,
depth: depth,
shortName: shortName,
type: doc.ngdoc,
keywords:doc.keywords()
});
});
words.sort(keywordSort);
return words;
}
var KEYWORD_PRIORITY = {
'.started': 1,
'.guide': 2,
'.guide.overview': 1,
'.angular': 7,
'.angular.Array': 7,
'.angular.Object': 7,
'.angular.directive': 7,
'.angular.filter': 7,
'.angular.formatter': 7,
'.angular.scope': 7,
'.angular.service': 7,
'.angular.validator': 7,
'.angular.widget': 7
};
function keywordSort(a, b){
function mangleName(doc) {
var path = doc.id.split(/\./);
var mangled = [];
var partialName = '';
path.forEach(function(name){
partialName += '.' + name;
mangled.push(KEYWORD_PRIORITY[partialName] || 5);
mangled.push(name);
});
return mangled.join('.');
}
var nameA = mangleName(a);
var nameB = mangleName(b);
return nameA < nameB ? -1 : (nameA > nameB ? 1 : 0);
}
//////////////////////////////////////////////////////////
function trim(text) {
var MAX_INDENT = 9999;
var empty = RegExp.prototype.test.bind(/^\s*$/);
var lines = text.split('\n');
var minIndent = MAX_INDENT;
var indentRegExp;
var ignoreLine = (lines[0][0] != ' ' && lines.length > 1);
// ignore first line if it has no indentation and there is more than one line
lines.forEach(function(line){
if (ignoreLine) {
ignoreLine = false;
return;
}
var indent = line.match(/^\s*/)[0].length;
if (indent > 0 || minIndent == MAX_INDENT) {
minIndent = Math.min(minIndent, indent);
}
});
indentRegExp = new RegExp('^\\s{0,' + minIndent + '}');
for ( var i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(indentRegExp, '');
}
// remove leading lines
while (empty(lines[0])) {
lines.shift();
}
// remove trailing
while (empty(lines[lines.length - 1])) {
lines.pop();
}
return lines.join('\n');
}
function indent(text, spaceCount) {
var lines = text.split('\n'),
indent = '',
fixedLines = [];
while(spaceCount--) indent += ' ';
lines.forEach(function(line) {
fixedLines.push(indent + line);
});
return fixedLines.join('\n');
}
//////////////////////////////////////////////////////////
function merge(docs){
var byName = {};
docs.forEach(function(doc){
byName[doc.name] = doc;
});
for(var i=0; i<docs.length;) {
if (findParent(docs[i], 'method') ||
findParent(docs[i], 'property')) {
docs.splice(i, 1);
} else {
i++;
}
}
function findParent(doc, name){
var parentName = doc[name+'Of'];
if (!parentName) return false;
var parent = byName[parentName];
if (!parent)
throw new Error("No parent named '" + parentName + "' for '" +
doc.name + "' in @" + name + "Of.");
var listName = (name + 's').replace(/ys$/, 'ies');
var list = parent[listName] = (parent[listName] || []);
list.push(doc);
list.sort(orderByName);
return true;
}
function orderByName(a, b){
return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0);
}
}
//////////////////////////////////////////////////////////
function property(name) {
return function(value){
return value[name];
};
}
+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;
+35
View File
@@ -0,0 +1,35 @@
@namespace doc url("http://docs.angularjs.org/");
doc\:example {
display: none;
}
ul.doc-example {
list-style-type: none;
position: relative;
font-size: 14px;
}
ul.doc-example > li {
border: 2px solid gray;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
background-color: white;
margin-bottom: 20px;
}
ul.doc-example > li.doc-example-heading {
border: none;
border-radius: none;
margin-bottom: -10px;
}
li.doc-example-live {
padding: 10px;
font-size: 1.2em;
}
div.syntaxhighlighter {
padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
}
+88
View File
@@ -0,0 +1,88 @@
(function(){
var angularJsUrl;
var scripts = document.getElementsByTagName("script");
var angularJsRegex = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/
for(var j = 0; j < scripts.length; j++) {
var src = scripts[j].src;
if (src && src.match(angularJsRegex)) {
angularJsUrl = src.replace('docs.angularjs.org', 'code.angularjs.org');
continue;
}
}
var HTML_TEMPLATE =
'<!doctype html>\n' +
'<html xmlns:ng="http://angularjs.org">\n' +
' <script src="' + angularJsUrl + '" ng:autobind></script>\n' +
' <body>\n' +
'_HTML_SOURCE_\n' +
' </body>\n' +
'</html>';
angular.widget('doc:example', function(element){
this.descend(true); //compile the example code
element.hide();
var example = element.find('doc\\:source').eq(0),
exampleSrc = example.text(),
scenario = element.find('doc\\:scenario').eq(0);
var code = indent(exampleSrc);
var tabHtml =
'<ul class="doc-example">' +
'<li class="doc-example-heading"><h3>Source</h3></li>' +
'<li class="doc-example-source" ng:non-bindable>' +
'<pre class="brush: js; html-script: true; highlight: [' +
code.hilite + ']; toolbar: false;"></pre></li>' +
'<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
'<li class="doc-example-live">' + exampleSrc +'</li>';
if (scenario.text()) {
tabHtml +=
'<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
'<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>';
}
tabHtml +=
'</ul>';
var tabs = angular.element(tabHtml);
tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html));
element.html('');
element.append(tabs);
element.show();
var script = (exampleSrc.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
try {
eval(script);
} catch (e) {
alert(e);
}
});
function indent(text) {
if (!text) return text;
var lines = text.split(/\n/);
var lineNo = [];
// remove any leading blank lines
while (lines[0].match(/^\s*$/)) lines.shift();
// remove any trailing blank lines
while (lines[lines.length - 1].match(/^\s*$/)) lines.pop();
var minIndent = 999;
for ( var i = 0; i < lines.length; i++) {
var line = lines[0];
var indent = line.match(/^\s*/)[0];
if (indent !== line && indent.length < minIndent) {
minIndent = indent.length;
}
}
for ( var i = 0; i < lines.length; i++) {
lines[i] = ' ' + lines[i].substring(minIndent);
lineNo.push(5 + i);
}
return {html: lines.join('\n'), hilite: lineNo.join(',') };
};
})();
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
<head>
<title>&lt;angular/&gt; Docs Scenario Runner</title>
<script type="text/javascript" src="../angular-scenario.js" ng:autobind></script>
<script type="text/javascript" src="docs-scenario.js"></script>
</head>
<body>
</body>
</html>
+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;
}
+53
View File
@@ -0,0 +1,53 @@
var HAS_HASH = /#/;
DocsController.$inject = ['$location', '$browser', '$window'];
function DocsController($location, $browser, $window) {
this.pages = NG_PAGES;
window.$root = this.$root;
this.$location = $location;
if (!HAS_HASH.test($location.href)) {
$location.hashPath = '!angular';
}
this.$watch('$location.hashPath', function(hashPath) {
if (hashPath.match(/^!/)) {
this.partialId = hashPath.substring(1);
this.partialTitle = (angular.Array.filter(NG_PAGES, {id:this.partialId})[0]||{}).name;
}
});
this.getUrl = function(page){
return '#!' + page.id;
};
this.getCurrentPartial = function(){
return './' + this.partialId + '.html';
};
this.getClass = function(page) {
var depth = page.depth,
cssClass = 'level-' + depth + (page.name == this.partialId ? ' selected' : '');
if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular';
return cssClass;
};
this.afterPartialLoaded = function() {
SyntaxHighlighter.highlight();
};
this.getFeedbackUrl = function() {
return "mailto:angular@googlegroups.com?" +
"subject=" + escape("Feedback on " + $location.href) + "&" +
"body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
};
}
// prevent compilation of code
angular.widget('code', function(element){
element.attr('ng:non-bindable', 'true');
});
SyntaxHighlighter['defaults'].toolbar = false;
+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;: {{partialTitle}}">&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">{{partialTitle}}</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
+74
View File
@@ -0,0 +1,74 @@
/**
* All writing related code here. This is so that we can separate the async code from sync code
* for testability
*/
require.paths.push(__dirname);
var fs = require('fs');
var OUTPUT_DIR = "build/docs/";
function output(docs, content, callback){
callback();
}
exports.output = function(file, content, callback){
//console.log('writing', OUTPUT_DIR + file, '...');
fs.writeFile(
OUTPUT_DIR + file,
exports.toString(content),
callback);
};
exports.toString = function toString(obj){
switch (typeof obj) {
case 'string':
return obj;
case 'object':
if (obj instanceof Array) {
obj.forEach(function (value, key){
obj[key] = toString(value);
});
return obj.join('');
} else {
return JSON.stringify(obj);
}
}
return obj;
};
exports.makeDir = function (path, callback) {
var parts = path.split(/\//);
path = '.';
(function next(){
if (parts.length) {
path += '/' + parts.shift();
fs.mkdir(path, 0777, next);
} else {
callback();
}
})();
};
exports.copy = function(filename, callback){
copy('docs/src/templates/' + filename, OUTPUT_DIR + filename, callback);
};
function copy(from, to, callback) {
//console.log('writing', to, '...');
fs.readFile(from, function(err, content){
if (err) return callback.error(err);
fs.writeFile(to, content, callback);
});
}
exports.copyDir = function(dir, callback) {
exports.makeDir(OUTPUT_DIR + '/' + dir, callback.waitFor(function(){
fs.readdir('docs/' + dir, callback.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
copy('docs/' + dir + '/' + file, OUTPUT_DIR + '/' + dir + '/' + file, callback.waitFor());
});
callback();
}));
}));
};
+122
View File
@@ -0,0 +1,122 @@
@workInProgress
@ngdoc overview
@name Getting Started
@description
# Hello World
The easiest way to get started with angular is to create a basic Hello World app.
# Step 1
In your favorite text editor, create a file called helloworld.html. Copy and paste the following
contents into the file:
<doc:example>
<doc:source>
Hello {{'World'}}!
</doc:source>
</doc:example>
# Step 2
<img class="right" src="img/helloworld.png"/>
Navigate to the file helloworld.html in your browser. The page should look like the screenshot
below:
That's it! Now take another look at helloworld.html. Here's what's going on behind the scenes:
<pre>
<html xmlns:ng="http://angularjs.org">
</pre>
Defines the namespace `ng`, which represents the URL `http://angularjs.org`. You must define the
`ng` namespace in your application so the browser understands angular directives like
`ng:autobind`.
<pre>
<script type="text/javascript" src="http://code.angularjs.org/angular-?.?.?.min.js" ng:autobind></script>
</pre>
Sets the `src` attribute of the script tag to `http://code.angularjs.org/angular-?.?.?.min.js` to
bootstrap to the angular environment. Uses the `ng:autobind` attribute to compile and manage the
whole HTML document. The compilation happens in the page's onLoad handler.
<pre>
Hello {{'World'}}!
</pre>
This is the template that describes how this element is displayed in the UI.
Uses the double curly brace markup (`{{}}`) to bind an expression to the greeting text. In this
case the expression is the string literal 'World'.
# Step 3
For a more advanced Hello World example that demonstrates angular's two-way data binding, edit
helloworld.html and replace the contents of the `<body/>` with:
<doc:example>
<doc:source>
Your name: <input type="text" name="yourname" value="World"/>
<hr/>
Hello {{yourname}}!
</doc:source>
</doc:example>
<img class="right" src="img/helloworld_2way.png"/>
These are the changes to note:
* The text input widget is bound to the text stored by the name variable.
* The name variable is implicit in the root scope.
* You did not need to explicitly register an event listener or define an event handler for events.
Now refresh the `helloworld.html` page in your browser. Your screen should now look like this:
Type your name into the input box and notice the immediate change to the displayed greeting. This
example demonstrates the concept of angular's two-way data binding; any changes to the input field
are immediately reflected in the greeting text.
# Anatomy of an angular app
These are the 3 parts of an angular app and how they map to the Model-View-Controller design pattern:
# Template
Templates, which you write in HTML and CSS, are the View. You add elements, attributes, and markup
to HTML, which are instructions to the angular compiler. These instructions are fully extensible,
meaning that you can build your own declarative language with angular.
# Application Logic and Behavior
Application Logic and Behavior, which you define in JavaScript, are the Controllers. Unlike
normal AJAX applications, you don't need to write additional listeners or DOM manipulators in
angular because they are built-in. This makes your application logic very easy to write, test,
maintain, and understand.
# Scope
<img class="right" src="img/angular_parts.png"/>
Scope, which is a JavaScript object that has the ability to watch for changes and get notified of
them, is the Model. You typically don't need to write much, if any, additional JavaScript to
define your model.
Additionally, angular comes with a set of Services, which have the following properties:
* Useful for building web applications.
* You can extend the services and add application-specific behavior to them.
* Examples include Dependency-Injection, XHR, caching, URL routing, and browser abstraction.
The following illustrates each part of an angular application and how they work together:
# Where to go next
For more hands-on examples of using angular, including more source code that you can copy and
paste into your own page, take a look through {@link cookbook}.
For explanations of the concepts described in this example, see {@link guide.bootstrap bootstrap},
{@link guide.template template}, {@link angular.widget.HTML input}, {@link angula.scope scope},
{@link angular.markup markup}, and {@link guide.data-binding data binding}.
To read about the HTML compiler and the compilation process, see {@link guide.compiler compiler}.
For more angular concepts, see the {@link guide Developer Guide}.
+18
View File
@@ -0,0 +1,18 @@
<label>Name:</label>
<input type="text" name="form.name" ng:required>
<div ng:repeat="contact in form.contacts">
<select name="contact.type">
<option>url</option>
<option>email</option>
<option>phone</option>
</select>
<input type="text" name="contact.url">
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<div>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
</div>
<button ng:click="cancel()">Cancel</button>
<button ng:click="save()">Save</button>
+5
View File
@@ -0,0 +1,5 @@
Hello {{person.name}},
<div>
Your contact information:
<div ng:repeat="contact in person.contacts">{{contact.type}}: {{contact.url|linky}}</div>
</div>
+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" />
@@ -1,7 +1,7 @@
describe('personal log', function() {
beforeEach(function() {
navigateTo('../personalLog.html');
browser().navigateTo('../personalLog.html');
});
@@ -64,8 +64,7 @@ describe('personal log', function() {
element('form input[type="submit"]').click();
expect(repeater('ul li').count()).toEqual(1);
navigateTo('about:blank');
navigateTo('../personalLog.html');
browser().reload();
expect(repeater('ul li').column('log.msg')).toEqual('my persistent message');
expect(repeater('ul li').count()).toEqual(1);
@@ -82,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>
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/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
+180
View File
@@ -0,0 +1,180 @@
var fs = require('fs');
var sys = require('sys');
var path = require('path');
var filename = __dirname + '/jasmine.js';
global.window = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval
};
var src = fs.readFileSync(filename);
var jasmine = process.compile(src + '\njasmine;', filename);
delete global.window;
function noop(){}
jasmine.executeSpecsInFolder = function(folder, done, isVerbose, showColors, matcher){
var log = [];
var columnCounter = 0;
var start = 0;
var elapsed = 0;
var verbose = isVerbose || false;
var fileMatcher = new RegExp(matcher || "\.js$");
var colors = showColors || false;
var specs = jasmine.getAllSpecFiles(folder, fileMatcher);
var ansi = {
green: '\033[32m',
red: '\033[31m',
yellow: '\033[33m',
none: '\033[0m'
};
for (var i = 0, len = specs.length; i < len; ++i){
var filename = specs[i];
require(filename.replace(/\.*$/, ""));
}
var jasmineEnv = jasmine.getEnv();
jasmineEnv.reporter = {
log: function(str){
},
reportSpecStarting: function(runner) {
},
reportRunnerStarting: function(runner) {
sys.puts('Started');
start = Number(new Date);
},
reportSuiteResults: function(suite) {
var specResults = suite.results();
var path = [];
while(suite) {
path.unshift(suite.description);
suite = suite.parentSuite;
}
var description = path.join(' ');
if (verbose)
log.push('Spec ' + description);
specResults.items_.forEach(function(spec){
if (spec.failedCount > 0 && spec.description) {
if (!verbose)
log.push(description);
log.push(' it ' + spec.description);
spec.items_.forEach(function(result){
log.push(' ' + result.trace.stack + '\n');
});
}
});
},
reportSpecResults: function(spec) {
var result = spec.results();
var msg = '';
if (result.passed())
{
msg = (colors) ? (ansi.green + '.' + ansi.none) : '.';
// } else if (result.skipped) { TODO: Research why "result.skipped" returns false when "xit" is called on a spec?
// msg = (colors) ? (ansi.yellow + '*' + ansi.none) : '*';
} else {
msg = (colors) ? (ansi.red + 'F' + ansi.none) : 'F';
}
sys.print(msg);
if (columnCounter++ < 50) return;
columnCounter = 0;
sys.print('\n');
},
reportRunnerResults: function(runner) {
elapsed = (Number(new Date) - start) / 1000;
sys.puts('\n');
log.forEach(function(log){
sys.puts(log);
});
sys.puts('Finished in ' + elapsed + ' seconds');
var summary = jasmine.printRunnerResults(runner);
if(colors)
{
if(runner.results().failedCount === 0 )
sys.puts(ansi.green + summary + ansi.none);
else
sys.puts(ansi.red + summary + ansi.none);
} else {
sys.puts(summary);
}
(done||noop)(runner, log);
}
};
jasmineEnv.execute();
};
jasmine.getAllSpecFiles = function(dir, matcher){
var specs = [];
if (fs.statSync(dir).isFile() && dir.match(matcher)) {
specs.push(dir);
} else {
var files = fs.readdirSync(dir);
for (var i = 0, len = files.length; i < len; ++i){
var filename = dir + '/' + files[i];
if (fs.statSync(filename).isFile() && filename.match(matcher)){
specs.push(filename);
}else if (fs.statSync(filename).isDirectory()){
var subfiles = this.getAllSpecFiles(filename, matcher);
subfiles.forEach(function(result){
specs.push(result);
});
}
}
}
return specs;
};
jasmine.printRunnerResults = function(runner){
var results = runner.results();
var suites = runner.suites();
var msg = '';
msg += suites.length + ' test' + ((suites.length === 1) ? '' : 's') + ', ';
msg += results.totalCount + ' assertion' + ((results.totalCount === 1) ? '' : 's') + ', ';
msg += results.failedCount + ' failure' + ((results.failedCount === 1) ? '' : 's') + '\n';
return msg;
};
function now(){
return new Date().getTime();
}
jasmine.asyncSpecWait = function(){
var wait = jasmine.asyncSpecWait;
wait.start = now();
wait.done = false;
(function innerWait(){
waits(10);
runs(function() {
if (wait.start + wait.timeout < now()) {
expect('timeout waiting for spec').toBeNull();
} else if (wait.done) {
wait.done = false;
} else {
innerWait();
}
});
})();
};
jasmine.asyncSpecWait.timeout = 4 * 1000;
jasmine.asyncSpecDone = function(){
jasmine.asyncSpecWait.done = true;
};
for ( var key in jasmine) {
exports[key] = jasmine[key];
}
+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.
+33 -21
View File
@@ -8,10 +8,8 @@ var DEFAULT_PORT = 8000;
function main(argv) {
new HttpServer({
'GET': (function() {
var servlet = new StaticServlet();
return servlet.handleRequest.bind(servlet)
})()
'GET': createServlet(StaticServlet),
'HEAD': createServlet(StaticServlet)
}).start(Number(argv[2]) || DEFAULT_PORT);
}
@@ -22,6 +20,11 @@ function escapeHtml(value) {
replace('"', '&quot;');
}
function createServlet(Class) {
var servlet = new Class();
return servlet.handleRequest.bind(servlet);
}
/**
* An Http server implementation that uses a map of methods to decide
* action routing.
@@ -61,11 +64,10 @@ HttpServer.prototype.handleRequest_ = function(req, res) {
}
};
/**
* Handles static content.
*/
function StaticServlet() {}
function StaticServlet() {}
StaticServlet.MimeMap = {
'txt': 'text/plain',
@@ -82,12 +84,14 @@ StaticServlet.MimeMap = {
StaticServlet.prototype.handleRequest = function(req, res) {
var self = this;
var path = ('./' + req.url.pathname).replace('//','/');
var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/, function(match, hex){
return String.fromCharCode(parseInt(hex, 16));
});
var parts = path.split('/');
if (parts[parts.length-1].charAt(0) === '.')
return self.sendForbidden_(req, res, path);
fs.stat(path, function(err, stat) {
if (err)
if (err)
return self.sendMissing_(req, res, path);
if (stat.isDirectory())
return self.sendDirectory_(req, res, path);
@@ -116,8 +120,8 @@ StaticServlet.prototype.sendMissing_ = function(req, res, path) {
res.write('<title>404 Not Found</title>\n');
res.write('<h1>Not Found</h1>');
res.write(
'<p>The requested URL ' +
escapeHtml(path) +
'<p>The requested URL ' +
escapeHtml(path) +
' was not found on this server.</p>'
);
res.end();
@@ -133,7 +137,7 @@ StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
res.write('<title>403 Forbidden</title>\n');
res.write('<h1>Forbidden</h1>');
res.write(
'<p>You do not have permission to access ' +
'<p>You do not have permission to access ' +
escapeHtml(path) + ' on this server.</p>'
);
res.end();
@@ -149,8 +153,8 @@ StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
res.write('<title>301 Moved Permanently</title>\n');
res.write('<h1>Moved Permanently</h1>');
res.write(
'<p>The document has moved <a href="' +
redirectUrl +
'<p>The document has moved <a href="' +
redirectUrl +
'">here</a>.</p>'
);
res.end();
@@ -164,13 +168,17 @@ StaticServlet.prototype.sendFile_ = function(req, res, path) {
'Content-Type': StaticServlet.
MimeMap[path.split('.').pop()] || 'text/plain'
});
file.on('data', res.write.bind(res));
file.on('close', function() {
if (req.method === 'HEAD') {
res.end();
});
file.on('error', function(error) {
self.sendError_(req, res, error);
});
} else {
file.on('data', res.write.bind(res));
file.on('close', function() {
res.end();
});
file.on('error', function(error) {
self.sendError_(req, res, error);
});
}
};
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
@@ -181,7 +189,7 @@ StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
return self.sendRedirect_(req, res, redirectUrl);
}
fs.readdir(path, function(err, files) {
if (err)
if (err)
return self.sendError_(req, res, error);
if (!files.length)
@@ -207,6 +215,10 @@ StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
res.writeHead(200, {
'Content-Type': 'text/html'
});
if (req.method === 'HEAD') {
res.end();
return;
}
res.write('<!doctype html>\n');
res.write('<title>' + escapeHtml(path) + '</title>\n');
res.write('<style>\n');
@@ -225,5 +237,5 @@ StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
res.end();
};
// Must be last,
// Must be last,
main(process.argv);

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