Compare commits

...

128 Commits

Author SHA1 Message Date
Igor Minar 2f128c9619 fix(e2e): add index-nocache.html to run e2e tests without cache
using appcache while running e2e tests was causing the following
problems:
- Safari would occasionally reload the app (as a result of the appcache
  refresh) during the angular.validator.asychronous test, which would
  result in test failure and false positivy.
- Firefox6 would run the tests very slowly, disabling the cache resolved
  the latency issues
- Sometimes tests would run with stale code pulled from cache, which
  would result in flaky tests.
2011-10-03 12:24:29 -07:00
Igor Minar f7a5f1788a fix($route): fix regex escaping in route matcher 2011-09-27 01:45:54 +02:00
Marcello Nuccio b3ed7a8a7a fix($resource): action defaults should override resource defaults
defaults definned per action should take precedence over defaults
defined for the whole resource.

This is potentialy a BREAKING CHANGE in case someone relied on the buggy
behavior.
2011-09-27 00:52:12 +02:00
Igor Minar 62d34e1437 fix(angular-mocks): fix .defer.cancel when i=0 2011-09-16 14:19:05 +02:00
Igor Minar 855971c385 fix(angular-mocks): fix forEach -> angular.forEach in $browser.defer.cancel 2011-09-16 01:52:30 +02:00
Igor Minar 2d489ff936 fix(docs): use window.execScript instead of window.eval on IE
IE's window.eval doesn't execute in the global context, so we have to
use window.execScript instead which works like window.eval on normal
browsers. However execScript throws an exception when an empty string is
passed in, so I created a workaround with a workaround.
2011-09-06 14:58:39 -07:00
Vojta Jina fc5cda2f72 fix($browser.xhr): not convert 0 status to 200 2011-09-06 23:33:29 +02:00
Igor Minar 05ad1ce90c test(coockbook/mvc): disable tic tac toe e2e test
it looks like under certain circumstances the location service doesn't
propagate chages to window.loction. I'm disabling this test now, it is
passing on the master branch (0.10.0)
2011-09-02 22:34:07 -07:00
Igor Minar 5703984d4d test(jsonp): fixing jsonp e2e tests
- buzz api keeps on throttling our requests which makes our build fail
  so I'm disabling the buzz demo e2e test
- the $xhr service jsonp test was modified to use jsonp on angularjs.org
  instead of buzz api for the same reason as mentioned above
2011-09-02 17:10:25 -07:00
Igor Minar ea8952177e fix(test): improve $cookie service test to work with Safari 5.1
the max size for safari cookies has changed sligtly so I had to adjust
the test to make cookie creation fail on this browser
2011-09-02 10:05:06 -07:00
Di Peng 90ca6f983e fix(docs): remove more unecessary use of hide() and show() method
- tutorial section of docs fails to render properly as
doc:tutorial-instructions widget uses deprecated show and hide methods
of jQlite.
2011-08-21 09:41:50 -07:00
Igor Minar 57030bb6b4 chore(version.yaml): preparing version.yaml for 0.9.20 2011-08-21 01:14:16 -07:00
Igor Minar 710a27030e cutting the 0.9.19 canine-psychokinesis release 2011-08-21 01:12:34 -07:00
Igor Minar 19aa16c8d5 fix(docs): work around the lame ng:show directive 2011-08-21 01:12:34 -07:00
Igor Minar 4a1972c71b fix(docs): change docs.css to avoid css clashes in buzz example 2011-08-21 01:12:34 -07:00
Igor Minar 6aa04b1db4 fix(ng:options): remove memory leak caused by scope.
$new can't be used for creation of temporary scopes because it registers
an onEval listener that doesn't go away and keeps the scope around, we
must use inherit(scope) instead to avoid this issue.

The issue does not apply to the master branch with the new scope which
has a scope descructor to clean up this mess.
2011-08-21 01:12:34 -07:00
Di Peng ac6e1306ec fix(sample): Fix for jsFiddle integration 2011-08-19 13:29:26 -07:00
Igor Minar e004378d10 feat($route): add reloadOnSearch route param to avoid reloads
In order to avoid unnecesary route reloads when just hashSearch part
of the url changes, it is now possible to disable this behavior by
setting reloadOnSearch param of the route declaration to false.

Closes #354
2011-08-19 12:05:52 -07:00
Karl Seamon 4ec1d8ee86 feat($xhr,$resource): expose response headers in callbacks
all $xhr*, $resource and related mocks now have access to headers from
their callbacks
2011-08-19 01:20:45 -07:00
Karl Seamon c37bfde9eb fix($resource): properly call error callback when resource is called with two arguments 2011-08-19 01:17:20 -07:00
Vojta Jina f6bcbb53f0 feat(test): toHaveBeenCalledOnce jasmine matcher 2011-08-19 01:17:09 -07:00
dandoyon 53a4580d95 doc(sample): Add javascript sandbox integration (jsFiddle)
Change doc_widget.js to:

- render "edit in jsfiddle" button next to all examples
- make opt out certain examples by adding jsfiddle="false" attribute to
  doc:source element
2011-08-19 01:16:56 -07:00
Igor Minar 4c8eaa1eb0 refactor(jqLite): remove jqLite show/hide support
it turns out that even with our tricks, jqLite#show is not usable in
practice and definitely not on par with jQuery. so rather than
introducing half-baked apis which introduce issues, I'm removing them.

I also removed show/hide uses from docs, since they are not needed.

Breaks jqLite.hide/jqLite.show which are no longer available.
2011-08-19 00:59:44 -07:00
Igor Minar 4ba35eb97e chore(jasmine): disable 'Jasmine waiting for..' msg 2011-08-19 00:15:21 -07:00
Di Peng 6fb4bf4c54 fix(directives): make ng:class-even/odd work with ng:class
Closes #508
2011-08-19 00:14:05 -07:00
Misko Hevery cc604b6e26 fix(bootstrap): missing var failed strict mode boot 2011-08-18 23:47:02 -07:00
Vojta Jina 99ee6f275a doc($browser): remove duplication of $browser to docs
This was causing to show up the "$browser" twice in the menu.
2011-08-18 23:46:51 -07:00
Vojta Jina 21c4919a5b doc($browser): hide $browser.notifyWhenNoOustandingRequest method
Closes #506
2011-08-18 23:46:21 -07:00
Di Peng 714759100c refactor(widgets): remove input[button, submit, reset, image] and button windgets
These widgets are useless and only trigger extra $updateViews.

The only reason we had them was to support ng:change on these widgets,
but since there are no bindings present in these cases it doesn't make
sense to support ng:change here. It's likely just a leftover from
getangular.com

Breaking change: ng:change for input[button], input[submit], input[reset], input[image]
and button widgets is not supported any more
2011-08-18 23:44:15 -07:00
Di Peng ee8e981c47 doc(xhr): add e2e test for JSONP error handling
- add e2e tests
- refactor the example by removing clear button and simplifying the code
2011-08-18 23:34:15 -07:00
Di Peng 05e2c3196c feat($browser): JSONP error handling
since we don't know if the error was due to a client error (4xx) or
server error (5xx), we leave the status code as undefined.
2011-08-18 23:33:32 -07:00
Igor Minar 718ebf1fcf doc(tutorial): updates needed for 0.9.18 rebase 2011-08-18 23:33:19 -07:00
DiPeng 2f5d17f3b6 fix(docs): fix qfs.read() encoding issue
- must use binary reading when using read function in q-fs module
otherwise some unicode character may be garbled.

Closes #497
2011-08-18 23:33:10 -07:00
Vojta Jina fd792de9e8 fix($xhr.error): fix docs and add missed breaking change
$xhr.error's first argument (request) has no callback property anymore,
it's called success now...

This breaking change was introduced by b5594a773a
2011-08-18 23:33:00 -07:00
dandoyon a01cf6d39e doc(typos): fix couple of typos in the docs
Minor documentation fixes. Should not be any code changes.
One test changed due to dependency on text in documentation.
2011-08-18 23:24:08 -07:00
Igor Minar f93e9bfa81 prepare the 0.9.19 canine-psychokinesis iteration 2011-08-18 23:24:08 -07:00
Igor Minar 590cd14ae0 fix(Rakefile): index-jq.html needs to be rewritten like index.html 2011-08-18 23:23:41 -07:00
Igor Minar 74db92cd9f doc(release notes): small fixes for the 0.9.18 release 2011-08-18 23:23:27 -07:00
Igor Minar aacd5b672e cutting the 0.9.18 jiggling-armfat release 2011-07-29 16:30:24 -07:00
Igor Minar 8d64793717 doc(release notes): release notes for the 0.9.18 jiggling-armfat release 2011-07-29 15:27:15 -07:00
Igor Minar 908f59a5df doc(date filter): fix dashes in api docs 2011-07-29 15:00:00 -07:00
Igor Minar a45d383da2 doc(contribute): add npm & q dependencies to setup instructions 2011-07-29 13:59:36 -07:00
Igor Minar c1a681d6f4 doc(index.html): change the order of elements in the navbar
Users often don't see Tutorial and go straight to crappy Dev Guide,
changing the order should help find them right content in the right
order.
2011-07-29 13:44:55 -07:00
Igor Minar f4df421b44 doc(css): make all navbar links bold 2011-07-29 13:43:51 -07:00
Igor Minar bdef462ccc doc(started): fixing up the doc and adding link to the tutorial 2011-07-29 12:56:50 -07:00
Igor Minar a79231dea6 doc(guide): various fixes and improvements 2011-07-29 12:46:54 -07:00
Igor Minar 3e54a1b18a doc(tutorial): fixes and improvements from Toni and Ben 2011-07-29 12:40:27 -07:00
Igor Minar 4b90f65614 feat(tutorial): add an arrow to 'Workspace Reset Instructions' links
The arrow is a hint that user won't navigate to a new page, but instead
the instructions will be displayed inline.
2011-07-29 11:43:17 -07:00
Karl Seamon b5594a773a feat($xhr): add custom error callback to $xhr, $xhr.cache, $xhr.bulk, $resource
Closes #408
2011-07-27 15:21:31 -07:00
Vojta Jina f39420e7d7 style(): fix couple of missing semi-colons 2011-07-27 22:24:07 +02:00
Vojta Jina 72e46548b8 test(filter.date): fix e2e test to pass on different time zones 2011-07-27 22:18:51 +02:00
Di Peng 9dea9de449 feat(docs): add full offline support 2011-07-26 16:35:42 -07:00
Di Peng bee78a8492 feat(docs): add a changelog link to the footer 2011-07-26 14:21:13 -07:00
Di Peng f3e04fbd6a fix(ng:show/ng:hide): use jqLite.show/jqLite.hide
The previous implementation didn't handle situation when in css
something was hidden with a cascaded display:none rule and then we
wanted to show it.

Unfortunatelly our test doesn't test this scenario because it's too
complicated. :-/
2011-07-26 14:21:13 -07:00
Vojta Jina 00ea08e0ab doc(tutorial): fix navigation widget to work without jQuery
jqLite doesn't support class selectors, can find only by tag name...
2011-07-26 14:20:24 -07:00
Di Peng 31b59efa96 feat(number/currency filter): format numbers and currency using pattern
both numbers and currency need to be formatted using a generic pattern
which can be replaced for a different pattern when angular is working in
a non en-US locale

for now only en-US locale is supported, but that will change in the
future
2011-07-26 14:16:57 -07:00
Igor Minar 17251372b1 style(ng:options): fix style and some docs 2011-07-26 10:11:40 -07:00
Misko Hevery f768954f38 fix(ng:options): add support for option groups
Closes# 450
2011-07-26 10:11:06 -07:00
Misko Hevery 3237f8b995 fix(directive): ng:options to support ng:change
Closes #463
2011-07-26 09:41:44 -07:00
Misko Hevery 7802c90e13 fix(directive): ng:options to support iterating over objects
Closes #448
2011-07-26 09:41:43 -07:00
Misko Hevery c348f2cad6 fix(directive): ng:options incorrectly re-grew options on datasource change
Closes #464
2011-07-26 09:41:42 -07:00
Misko Hevery f3456dc282 fix(directive): ng:options now support binding to expression
Closes #449
2011-07-26 09:41:41 -07:00
Misko Hevery ee04141a5a style(warnings): prevent the browser from making bogus GET requests during tests 2011-07-26 09:40:29 -07:00
Misko Hevery 66fec10dc3 style(warnings): added missing semi colons 2011-07-26 09:40:29 -07:00
Misko Hevery ae75c35746 chore(jqlite): clean up dead code 2011-07-26 09:40:29 -07:00
Misko Hevery 0cf5535333 doc(ng:view): fix broken template links in docs; add scenario test. 2011-07-26 09:40:29 -07:00
Igor Minar fdd5d9471f chore(license): update license headers + add version num
- fixed copyright overnship
- updated copyright years
- added @license tag so that closure compiler preserves the header
- added version number into headers (finally!)
2011-07-22 15:49:10 -07:00
Di Peng 0782422d1f feat(angular.version): add angular.version
- placeholders are replaced with actual angular versions when doing
rake compile
2011-07-22 15:34:55 -07:00
Di Peng 8fa066190a refactor(gen-docs): use q, qq, q-fs (node modules) to write gen-docs
- re-write gendocs.js, reader.js and writer.js
- all calls are asynchronous
2011-07-20 17:33:18 -07:00
Di Peng e90b741c94 feat(gen-docs): enable caching the whole site
Generate a manifest file automatically by reading the directories.
2011-07-20 17:09:40 -07:00
Di Peng 3af1e7ca2e feat(filter.date): add support for default datetime formats in en
- add support for full,long, medium, short datetime formats in en

Breaks MMMMM. now we don't support MMMMM anymore as old implementation differs
from Unicode Locale Data format we are following.

- removed support for fullDateTime and fullTime as it means too much
trouble with full timeZone names
- added docs for the new features
2011-07-20 17:06:56 -07:00
Di Peng 0fbaa2f12a feat(TzDate): add mock "toString" method to TzDate.
- If the third param of TzDate constructor is defined, toStirng will
just return this third parameter. Otherwise, toString will still
be treated as unimplemented method
2011-07-20 16:50:44 -07:00
Igor Minar ad3b8d7bcf chore(docs/.htaccess): bundle .htaccess with docs 2011-07-19 16:58:40 -07:00
Igor Minar 3ea2416f80 Revert "fix(ng:class): preserve classes added post compilation"
This reverts commit 2428907259.

We decided to revert this because it is not bullet proof. The issue is
that we can't reliably have both angular and non-angular code in charge
of the DOM. We could work around some issues here and there, but we
can't do it reliably, so it's better not to support DOM manipulation
that happens outside of angular. There is a good chance that once we
integrate with MDVs our possition will change, but until then our
position is that only angular or angular widgets/directives can change
change DOM that was compiled.
2011-07-19 16:07:25 -07:00
Igor Minar 9636160332 doc(.defer.cancel): temporarily disable the doc 2011-07-19 14:04:50 -07:00
Igor Minar 2b2df4754d feat($browser.$defer.cancel): support canceling defered tasks 2011-07-18 14:14:19 -07:00
Igor Minar 120701b9d9 fix($browser.setUrl): make browser.setUrl more efficient
- browser should remember the last value retrieved via browser.getUrl
- browser should update window.location only if the new value is
  different from the current window.location value
2011-07-18 14:14:19 -07:00
Igor Minar fe5240732d feat(strict mode): adding strict mode flag to all js files
the flag must be in all src and test files so that we get the benefit of
running in the strict mode even in jstd

the following script was used to modify all files:

for file in `find src test -name "*.js"`; do
  echo -e "'use strict';\n" > temp.txt
  cat $file >> temp.txt
  mv temp.txt $file
done
2011-07-18 12:12:55 -07:00
Igor Minar b98c23274b feat(strict mode): turn on ECMAScript 5 strict mode
- add 'use strict'; statement to the prefix file
- configure closure compiler to use the ES5 strict mode
- strip all file-specific strict mode flags after concatination

Closes #223
2011-07-18 12:12:55 -07:00
Igor Minar 4c6d26a38f fix(strict mode): fix all issues discovered by strict mode and unit/e2e tests 2011-07-18 12:12:54 -07:00
Igor Minar c43ce91b25 chore(closure-compiler): upgrading to v20110615 2011-07-18 12:12:54 -07:00
Igor Minar b7cf7f2a79 doc(angular.annotate): properly disable doc snippet to avoid compiler warnings 2011-07-18 12:12:54 -07:00
Igor Minar ef7cf60ebd doc(misc): fixing typos in docs 2011-07-18 00:04:38 -07:00
DiPeng 7974e7eb5f refactor($browser): hide startPoll and poll methods
Breaks $browser.poll() method is moved inline to $browser.startpoll()
Breaks $browser.startpoll() method is made private
Refactor tests to reflect updated browser API

Closes #387
2011-07-18 00:04:14 -07:00
Di Peng f9b4c9da64 refactor(docs): run e2e tests with and without jquery
- e2e tests will run index.html (without jquery) and with
index-jq.html(with jquery).
- many small changes to make e2e tests work withough JQuery as we
discover problems that were previously hidden by using real JQuery.
2011-07-17 22:19:08 -07:00
Di Peng 83ac1193f2 style(jqLiteSpec): add space 2011-07-17 22:19:08 -07:00
Di Peng 7a3fdda965 feat(jqlite): added show(),hide() and eq() methods to jqlite
- add those three methods to jqlite
2011-07-17 22:19:08 -07:00
Igor Minar b4f18fc295 style(injector): remove extra semicolon 2011-07-18 04:47:39 +00:00
Igor Minar da464683aa doc(ng:include): improve the doc example to avoid confusion 2011-07-16 22:01:19 -07:00
Igor Minar a0b35161a6 fix(doc): fix all broken links 2011-07-16 01:15:37 -07:00
Igor Minar a8f4d87be5 doc(css): add '#content-list .level-4' css 2011-07-16 01:12:29 -07:00
Igor Minar 57ea8156a1 doc(ngdoc): add 'this' and 'returns' section for methods 2011-07-16 01:12:29 -07:00
Igor Minar 6289d18e61 doc(ngdoc): fix usage format for functions bolted onto services 2011-07-16 01:12:29 -07:00
Igor Minar 9e37ebe635 test(ngdoc): add test for @ngdoc function 2011-07-16 01:12:29 -07:00
Igor Minar 345c01c81b test(ngdoc): fix a typo in the @deprecated spec 2011-07-16 01:12:29 -07:00
Igor Minar 975aef2ad2 test(ngdoc): fix failing tests 2011-07-16 01:12:28 -07:00
Di Peng c863514660 doc(angular.mock.service.$browser): add xhr docs 2011-07-16 01:12:28 -07:00
Vojta Jina 86a6cc7152 chore(configs): Add missing files to jstd config
Add jstd-scenario-adapter files into jstd configs (jquery, coverage).

Remove angular.prefix, sufifix from exclude, as they don't have to be there.
They are not included, because of *.js mask.
2011-07-14 10:59:30 +02:00
Vojta Jina 8f3276bbcd chore(scripts): add test-jquery.sh for running unit tests with jQuery 2011-07-14 10:59:25 +02:00
Di Peng 2428907259 fix(ng:class): preserve classes added post compilation
- make sure ng:class preserve classes added after compilation

Closes #355
2011-07-13 16:42:38 -07:00
Igor Minar 8a8a2cf462 refactor($browser.xhr): use $browser.addJs for JSONP
There is no reason why we shouldn't reuse $browser.addJs for JSONP
requests.
2011-07-13 16:21:08 -07:00
Igor Minar 47efe44a1d fix($browser.addJs): make addJs jQuery compatible
Change addJs implementation to avoid use of jQuery because of issues
that affect angular-ie-compat.js. See inlined comment for more info.
2011-07-13 16:21:08 -07:00
Igor Minar c52e749a6e fix($browser.xhr): properly delete jsonp callbacks 2011-07-13 16:21:08 -07:00
Igor Minar 4ab3596295 fix(ie-compat): escape \ in regexp 2011-07-13 16:21:07 -07:00
Igor Minar 106674ac1e style(ie-compat): improved generated ie compat code 2011-07-13 16:21:07 -07:00
Vojta Jina 330d1a870d fix(bootstrap): Fix bootstrap on IE<8
No reason for including ie-compat in bootstrap, it's included during angularInit.

Fix including ie-compat even for production.
2011-07-13 16:21:07 -07:00
Vojta Jina 7e2e7b07b6 doc($route): fix $route example and couple of typos
Rewrite $route example a bit, as it required $location and $route services
to be eager published in the root scope.

Fix small typos in formatter and ng:options docs.
2011-07-13 11:49:16 +02:00
Vojta Jina ce80576e0b fix:jqLite: Set event.target on IE<8
IE<8's Event has not target property - it has srcElement property.
Fix that to be consistent as jQuery.
2011-07-12 23:04:46 -07:00
Vojta Jina 10da625ed9 fix:jqLite: Normalize non-existing attributes to undefined as jQuery
jqLite was returning null, but jQuery returns undefined
2011-07-12 23:04:46 -07:00
Vojta Jina 9ee9ca13da fix:jqLite: Fix binding to more events separated by space
The var eventHandler was defined outside forEach loop, so registering more
events caused calling listeners registered by the last one.

Regression:
elm.bind('click keyup', callback1);
elm.bind('click', callback2);
elm.bind('keyup', callback3);

Firing click event would have executed callback1, callback3 !
2011-07-12 23:04:46 -07:00
Igor Minar bb39d34279 test(ng:repeat): add tests for $position for small arrays/objects 2011-07-12 22:49:12 -07:00
Igor Minar e09a78438f doc:widget: improve angular.widget docs 2011-07-11 12:19:42 -07:00
Vojta Jina 1e890863e5 docs:compile: Just fixing some typos in the docs 2011-07-10 19:29:46 +02:00
Igor Minar 76a500179d docs:template: rename <angular/> to AngularJS 2011-07-08 08:24:09 -07:00
Igor Minar 7b32c71386 docs:css: remove bullet points from tutorial sidebar 2011-07-08 08:24:09 -07:00
Igor Minar 28e84ca167 docs:css: fix indentation for pre element 2011-07-08 08:24:09 -07:00
Igor Minar 41250e9cf9 prepare the jiggling-armfat iteration 2011-07-08 08:23:30 -07:00
Mårten Dolk 77ba539f63 fix:injector: make injector compatible with Rhino (HtmlUnit) 2011-07-06 16:06:54 -07:00
Igor Minar 952225f020 doc:dependencies: add /api/ prefix to dependency links 2011-07-02 10:37:04 -07:00
Igor Minar 35f9f527d3 doc:changelog: small changelog fixes 2011-07-02 08:44:50 -07:00
Igor Minar 30bd04feaa fix:exceptionHandler mock: should not specify dependencies
also added a test for this mock service
2011-07-01 18:17:54 -07:00
Igor Minar 25a77c58c1 fix:jstd.conf: explicitly specify script load order
Originally we relied on a lot of globbing, which resulted in
angular-mocks being loaded before normal services, so we never overwrote
services like $exceptionHandler with mocks. Explict definition
guarantees that we don't fall into the loading order trap, but requires
us to remember to update the jsTestDriver.conf file every time we
add/rename/remove a js file.
2011-07-01 18:17:42 -07:00
Igor Minar 75721223b5 fix:$browser mock: defer.flush() should flush all fns 2011-07-01 17:03:50 -07:00
DiPeng f606ffed4b doc:markup: wrong spelling for Vojta, all DI's fault. 2011-06-30 20:24:17 -07:00
Igor Minar b49035a8c5 preparing the 0.9.18 jiggling-armfat iteration 2011-06-30 14:29:28 -07:00
Igor Minar 86ff9dee23 docs:include: improve docs 2011-06-30 11:33:36 -07:00
Igor Minar 6dc22fe575 feat:build: better build/pkg/ dir structure
rake package now produced directory structure that can be uploaded to
the ftp server as is without manual changes
2011-06-30 10:58:14 -07:00
Igor Minar 68ab0f9b02 docs:changelog: add release date for 0.9.17 2011-06-30 10:54:24 -07:00
166 changed files with 3783 additions and 1520 deletions
+1
View File
@@ -9,3 +9,4 @@ performance/temp*.html
.idea/workspace.xml
*~
angular.js.tmproj
node_modules
+114 -5
View File
@@ -1,5 +1,109 @@
<a name="0.9.19"><a/>
# 0.9.19 canine-psychokinesis (2011-08-20) #
## Features
- added error handling support for JSONP requests (see error callback param of the [$xhr] service)
([commit](https://github.com/angular/angular.js/commit/05e2c3196c857402a9aa93837b565e0a2736af23))
- exposed http response headers in the [$xhr] and [$resource] callbacks
([commit](https://github.com/angular/angular.js/commit/4ec1d8ee86e3138fb91543ca0dca28463895c090)
contributed by Karl Seamon)
- added `reloadOnSearch` [$route] param support to prevent unnecessary controller reloads and
resulting flicker
([commit](https://github.com/angular/angular.js/commit/e004378d100ce767a1107180102790a9a360644e))
## Fixes
- make ng:class-even/odd compatible with ng:class
(Issue [#508](https://github.com/angular/angular.js/issues/508))
- fixed error handling for resources that didn't work in certain situations
([commit](https://github.com/angular/angular.js/commit/c37bfde9eb31556ee1eb146795b0c1f1504a4a26)
contributed by Karl Seamon)
## Docs
- [jsFiddle](http://jsfiddle.net/) integration for all docs.angularjs.org examples (contributed by
Dan Doyon).
## Breaking Changes
- removed [jqLite] show/hide support. See the
[commit](https://github.com/angular/angular.js/commit/4c8eaa1eb05ba98d30ff83f4420d6fcd69045d99)
message for details. Developers should use jquery or jqLite's `css('display', 'none')` and
`css('display', 'block'/'inline'/..)` instead
<a name="0.9.18"><a/>
# 0.9.18 jiggling-armfat (2011-07-29) #
### Features
- [ECMAScript 5 Strict Mode](https://developer.mozilla.org/en/JavaScript/Strict_mode) compliance
- [jqLite]
- added `show()`, `hide()` and `eq()` methods to jqlite
([commit](https://github.com/angular/angular.js/commit/7a3fdda9650a06792d9278a8cef06d544d49300f))
- added $defer.cancel to support cancelation of tasks defered via the [$defer] service
- [date] filter
- added support for `full`, `long`, `medium` and `short` date-time format flags
([commit](https://github.com/angular/angular.js/commit/3af1e7ca2ee8c2acd69e5bcbb3ffc1bf51239285))
- added support for `z` flag, which stands for short string timezone identifier, e.g. PST
- internal improvements to enable localization of date filter output
- [number] filter
- internal improvements to enable localization of number filter output
- [currency] filter
- support for custom currency symbols via an optional param
- internal improvements to enable localization of number filter output
- added [angular.version] for exposing the version of the loaded angular.js file
- updated angular.js and angular.min.js file headers with angular version and shorter & updated
license info
- [ng:options]
- support binding to expression (Issue [#449](https://github.com/angular/angular.js/issues/449))
- support iterating over objects (Issue [#448](https://github.com/angular/angular.js/issues/448))
- support ng:change (Issue [#463](https://github.com/angular/angular.js/issues/463))
- support option groups (`<optgroup>`)
(Issue [#450](https://github.com/angular/angular.js/issues/450))
- [$xhr] and [$resource] support for per-request error callbacks (Issue
[#408](https://github.com/angular/angular.js/issues/408)) (contributed by Karl Seamon)
### Bug Fixes
- make injector compatible with Rhino (HtmlUnit) (contributed by Mårten Dolk)
[commit](https://github.com/angular/angular.js/commit/77ba539f630c57b17d71dbf1e9c5667a7eb603b7)
- `ie-compat.js` fixes and improvements related to fetching this file on the fly on legacy browsers
- [jqLite]
- fix `bind()` when binding to more events separated by space
[commit](https://github.com/angular/angular.js/commit/9ee9ca13da3883d06733637f9048a83d94e6f1f8)
- non-existing attributes should return undefined just like in jQuery
[commit](https://github.com/angular/angular.js/commit/10da625ed93511dbf5d4e61ca4e42f6f2d478959)
- set event.target for IE<8
[commit](https://github.com/angular/angular.js/commit/ce80576e0b8ac9ed5a5b1f1a4dbc2446434a0002)
- improved implementation of [ng:show] and [ng:hide] directives by using jqLite/jQuery hide and
show methods
- [ng:options]
- fix incorrect re-growing of options on datasource change
(Issue [#464](https://github.com/angular/angular.js/issues/464))
### Docs
- added full offline support for docs (click on the link in the footer of docs.angularjs.org)
- many content improvements and corrections across all docs (reference api, tutorial, dev guide)
- many small design improvements
### Other
- doubled our e2e test suite by running all angular e2e tests with jqLite in addition to jQuery
### Breaking changes
- [commit](https://github.com/angular/angular.js/commit/3af1e7ca2ee8c2acd69e5bcbb3ffc1bf51239285)
removed support for the `MMMMM` (long month name), use `MMMM` instead. This was done to align
Angular with
[Unicode Technical Standard #35](http://unicode.org/reports/tr35/#Date_Format_Patterns) used by
Closure, as well as, future DOM apis currently being proposed to w3c.
- `$xhr.error`'s `request` argument has no `callback` property anymore, use `success` instead
<a name="0.9.17"><a/>
# <angular/> 0.9.17 vegetable-reanimation (in progress) #
# <angular/> 0.9.17 vegetable-reanimation (2011-06-30) #
### New Features
- New [ng:options] directive to better bind a model to `<select>` and `<option>` elements.
@@ -25,7 +129,7 @@
- $service now has $service.invoke for method injection ($service(self, fn) no longer works)
- injection name inference no longer supports method curry and linking functions. Both must be
explicitly specified using $inject property.
- Dynamic iteration (ng:repeat) on <option> elements is no longer supported. Use ng:options
- Dynamic iteration (ng:repeat) on `<option>` elements is no longer supported. Use ng:options
- Removal of index formatter (`ng:format="index"`) since its only use was with repeated `<options>`
(see above).
- Calling [$orderBy] without a predicate now returns the original unsorted array, instead of
@@ -74,7 +178,7 @@
### Bug Fixes
- reverted [ng:view] sync cache fix due to regression in the order of initialization of parent
and child controllers. (commits 9bd2c396 and 3d388498)
- [$resource] success callback is now executed whenever the http status code is <200,300>
- [$resource] success callback is now executed whenever the http status code is `<200,300>`
### Docs
@@ -197,8 +301,7 @@
- many, but by far not all, docs were updated, improved and cleaned up
### Features
- [`$route`](http://docs.angularjs.org/#!/api/angular.service.$route) service now supports these
features:
- [$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()`
@@ -550,17 +653,23 @@ with the `$route` service
[ng:checked]: http://docs.angularjs.org/#!/api/angular.directive.ng:checked
[ng:multiple]: http://docs.angularjs.org/#!/api/angular.directive.ng:multiple
[ng:readonly]: http://docs.angularjs.org/#!/api/angular.directive.ng:readonly
[ng:show]: http://docs.angularjs.org/#!/api/angular.directive.ng:show
[ng:hide]: http://docs.angularjs.org/#!/api/angular.directive.ng:hide
[$defer]: http://docs.angularjs.org/#!/api/angular.service.$defer
[$cookies]: http://docs.angularjs.org/#!/api/angular.service.$cookies
[$xhr]: http://docs.angularjs.org/#!/api/angular.service.$xhr
[$xhr.cache]: http://docs.angularjs.org/#!/api/angular.service.$xhr.cache
[$resource]: http://docs.angularjs.org/#!/api/angular.service.$resource
[$route]: http://docs.angularjs.org/#!/api/angular.service.$route
[$orderBy]: http://docs.angularjs.org/#!/api/angular.Array.orderBy
[date]: http://docs.angularjs.org/#!/api/angular.filter.date
[number]: http://docs.angularjs.org/#!/api/angular.filter.number
[currency]: http://docs.angularjs.org/#!/api/angular.filter.currency
[directive]: http://docs.angularjs.org/#!/api/angular.directive
[ng:autobind]: http://docs.angularjs.org/#!/api/angular.directive.ng:autobind
[guide.di]: http://docs.angularjs.org/#!/guide/dev_guide.di
[downloading]: http://docs.angularjs.org/#!/misc/downloading
[contribute]: http://docs.angularjs.org/#!/misc/contribute
[jqLite]: http://docs.angularjs.org/#!/api/angular.element
[angular.version]: http://docs.angularjs.org/#!/api/angular.version
[Jstd Scenario Adapter]: https://github.com/angular/angular.js/blob/master/src/jstd-scenario-adapter/Adapter.js
+85 -22
View File
@@ -64,6 +64,16 @@ task :default => [:compile, :test]
desc 'Init the build workspace'
task :init do
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
v = YAML::load( File.open( 'version.yaml' ) )
match = v['version'].match(/^([^-]*)(-snapshot)?$/)
NG_VERSION = Struct.new(:full, :major, :minor, :dot, :codename).
new(match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : ''),
match[1].split('.')[0],
match[1].split('.')[1],
match[1].split('.')[2],
v['codename'])
end
@@ -88,7 +98,7 @@ task :compile_scenario => :init do
concat = 'cat ' + deps.flatten.join(' ')
File.open(path_to('angular-scenario.js'), 'w') do |f|
f.write(%x{#{concat}})
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
f.write(gen_css('css/angular.css') + "\n")
f.write(gen_css('css/angular-scenario.css'))
end
@@ -106,7 +116,7 @@ task :compile_jstd_scenario_adapter => :init do
concat = 'cat ' + deps.flatten.join(' ')
File.open(path_to('jstd-scenario-adapter.js'), 'w') do |f|
f.write(%x{#{concat}})
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
end
# TODO(vojta) use jstd configuration when implemented
@@ -157,11 +167,13 @@ task :generate_ie_compat => :init do
# generate a javascript closure that contains a function which will append the generated css
# string as a stylesheet to the current html document
jsString = "(function(){ \r\n" +
" var jsUri = document.location.href.replace(/\\/[^\/]+(#.*)?$/, '/') + " +
" document.getElementById('ng-ie-compat').src; \r\n" +
" var css = '#{cssString}' \r\n" +
" var s = document.createElement('style'); \r\n" +
" var jsUri = document.location.href.replace(/\\/[^\\\/]+(#.*)?$/, '/') + \r\n" +
" document.getElementById('ng-ie-compat').src,\r\n" +
" css = '#{cssString}',\r\n" +
" s = document.createElement('style'); \r\n" +
"\r\n" +
" s.setAttribute('type', 'text/css'); \r\n" +
"\r\n" +
" if (s.styleSheet) { \r\n" +
" s.styleSheet.cssText = css; \r\n" +
" } else { \r\n" +
@@ -186,32 +198,45 @@ task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter, :gen
File.open(path_to('angular.js'), 'w') do |f|
concat = 'cat ' + deps.flatten.join(' ')
f.write(%x{#{concat}})
content = %x{#{concat}}.
gsub('"NG_VERSION_FULL"', NG_VERSION.full).
gsub('"NG_VERSION_MAJOR"', NG_VERSION.major).
gsub('"NG_VERSION_MINOR"', NG_VERSION.minor).
gsub('"NG_VERSION_DOT"', NG_VERSION.dot).
gsub('"NG_VERSION_CODENAME"', NG_VERSION.codename).
gsub(/^\s*['"]use strict['"];?\s*$/, ''). # remove all file-specific strict mode flags
gsub(/'USE STRICT'/, "'use strict'") # rename the placeholder in angular.prefix
f.write(content)
f.write(gen_css('css/angular.css', true))
end
%x(java -jar lib/closure-compiler/compiler.jar \
--compilation_level SIMPLE_OPTIMIZATIONS \
--language_in ECMASCRIPT5_STRICT \
--js #{path_to('angular.js')} \
--js_output_file #{path_to('angular.min.js')})
end
desc 'Generate docs'
task :docs do
task :docs => [:init] do
`node docs/src/gen-docs.js`
File.open(path_to('docs/.htaccess'), File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('"NG_VERSION_FULL"', NG_VERSION.full)
end
end
desc 'Create angular distribution'
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]) : '')
tarball = "angular-#{NG_VERSION.full}.tgz"
tarball = "angular-#{version}.tgz"
pkg_dir = path_to("pkg/angular-#{version}")
pkg_dir = path_to("pkg/angular-#{NG_VERSION.full}")
FileUtils.rm_r(path_to('pkg'), :force => true)
FileUtils.mkdir_p(pkg_dir)
@@ -223,33 +248,71 @@ task :package => [:clean, :compile, :docs] do
path_to('jstd-scenario-adapter.js'),
path_to('jstd-scenario-adapter-config.js'),
].each do |src|
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{version}\\1")
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{NG_VERSION.full}\\1")
FileUtils.cp(src, pkg_dir + '/' + dest)
end
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{version}"
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{NG_VERSION.full}"
File.open("#{pkg_dir}/docs-#{version}/index.html", File::RDWR) do |f|
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{version}.min.js")
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{version}/docs-scenario.html", File::RDWR) do |f|
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular-scenario.js', "angular-scenario-#{version}.js")
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{version}/app-cache.manifest", File::RDWR) do |f|
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-nocache.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{version}.min.js")
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-nocache.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular-scenario.js', "angular-scenario-#{NG_VERSION.full}.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache.manifest", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
end
%x(tar -czf #{path_to(tarball)} -C #{path_to('pkg')} .)
FileUtils.cp path_to(tarball), pkg_dir
FileUtils.mv pkg_dir, path_to(['pkg', NG_VERSION.full])
puts "Package created: #{path_to(tarball)}"
end
+1 -1
View File
@@ -53,7 +53,7 @@ to retrieve Buzz activity and comments.
</div>
</doc:source>
<doc:scenario>
it('fetch buzz and expand', function() {
xit('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();
+1 -1
View File
@@ -93,7 +93,7 @@ no connection between the controller and the view.
</div>
</doc:source>
<doc:scenario>
it('should play a game', function(){
xit('should play a game', function(){
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
@@ -8,7 +8,7 @@ We want this HTML source:
<pre>
<div ng:init="s='Hello'; n='World'">
<my:greeter salutation="s" name="n"/>
<my:greeter salutation="s" name="n"></my:greeter>
</div>
</pre>
@@ -23,9 +23,9 @@ to produce this DOM:
</div>
</pre>
That is, the new `<my:greeter/>` tag's `salutation` and `name` attributes should be transformed by
the compiler such that two `<span>` tags display the values of the attributes, with CSS classes
applied to the output.
That is, the new `<my:greeter></my:greeter>` tag's `salutation` and `name` attributes should be
transformed by the compiler such that two `<span>` tags display the values of the attributes, with
CSS classes applied to the output.
The following code snippet shows how to write a following widget definition that will be processed
by the compiler. Note that you have to declare the {@link dev_guide.bootstrap namespace} `my` in
@@ -41,9 +41,9 @@ angular.widget('my:greeter', function(compileElement){
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(' ');
linkElement.append(nameSpan);
linkElement.append(compiler.text('!'));
linkElement.append('!');
this.$watch(salutationExp, function(value){
salutationSpan.text(value);
});
@@ -57,12 +57,9 @@ 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.after(text.substring(index + 3));
textNode.after(angular.element('<hr>'));
textNode.after(text.substring(0, index));
textNode.remove();
}
});
@@ -11,7 +11,7 @@ expression and `alert()` the user with each new value:
<pre>
// An element widget
<my:watch exp="name"/>
<my:watch exp="name"></my:watch>
</pre>
You can implement `my:watch` like this:
@@ -36,8 +36,8 @@ Let's implement the same widget as in the example in Defining an Element Widget,
an attribute that can be added to any existing DOM element:
<pre>
// An attribute widget (my-watch) in a div tag
<div my-watch="name">text</div>
// An attribute widget (my:watch) in a div tag
<div my:watch="name">text</div>
</pre>
You can implement `my:watch` attribute like this:
<pre>
@@ -45,7 +45,7 @@ angular.widget('@my:watch', function(expression, compileElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value){
currentScope.$watch(expression, function(value) {
alert(value);
});
};
@@ -87,14 +87,8 @@ function fnB($window, serviceA_, name){};
</pre>
If angular does not find a `$inject` annotation on the function, then it calls the `.toString()`
method and tries to infer what should be injected using the following rules:
* Any argument starting with `$` is an angular service and will be added to the `$inject` property
array
* Any argument ending with `_` will be added to the `$inject` property array (angular strips the
`_`)
* All arguments following an argument which has neither `$` nor `_` , must not have `$` nor `_`
(these are free arguments for {@link http://en.wikipedia.org/wiki/Currying currying})
method and tries to infer what should be injected by using function argument names as dependency
identifiers.
**IMPORTANT**
Minifiers/obfuscators change the names of function arguments and will therefore break the `$inject`
@@ -39,7 +39,7 @@ only, not recommended for real applications):
Angular creates models implicitly (by creating a scope property and assigning it a suitable value)
when processing the following template constructs:
* Form input, select, and textarea elements:
* Form input, select, textarea and other form elements:
<input name="query" value="fluffy cloud">
+5 -9
View File
@@ -43,21 +43,17 @@ easier a web developer's life can if they're using angular:
<doc:example>
<doc:source>
<b>Invoice:</b>
<br/>
<br/>
<br />
<br />
<table>
<tr><td> </td><td> </td>
<tr><td>Quantity</td><td>Cost</td></tr>
<tr>
<td><input name="qty" value="1"
ng:validate="integer:0"
ng:required/></td>
<td><input name="cost" value="19.95"
ng:validate="number"
ng:required/></td>
<td><input name="qty" value="1" ng:validate="integer:0" ng:required /></td>
<td><input name="cost" value="19.95" ng:validate="number" ng:required /></td>
</tr>
</table>
<hr>
<hr />
<b>Total:</b> {{qty * cost | currency}}
</doc:source>
<!--
@@ -4,39 +4,42 @@
@description
Following is a unit test for the service in the example in {@link
dev_guide.services.registering_services Registering Angular Services}. The unit test example uses
Jasmine spy (mock) instead of a real browser alert.
dev_guide.services.creating_services Creating Angular Services}. The unit test example uses Jasmine
spy (mock) instead of a real browser alert.
<pre>
var mock, notify;
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
notify = angular.service('notify')(mock);
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();
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");
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"]);
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>
+9
View File
@@ -87,6 +87,15 @@ Rake website.
development web server. Depending on your system, you can install Node either from source or as a
pre-packaged bundle.
You'll also need npm and the following npm modules:
* install npm: `curl http://npmjs.org/install.sh | sh`
* install q: `npm install q`
* install qq: `npm install qq`
* install q-fs: `npm install q-fs`
* Java: The Java runtime is used to run {@link http://code.google.com/p/js-test-driver
JsTestDriver} (JSTD), which we use to run our unit test suite. JSTD binaries are part of the
`angular` source base, which means there is no need to install or configure it separately.
+16 -13
View File
@@ -4,7 +4,7 @@
# Hello World!
A great way for you to get started with `angular` is to create the tradtional
A great way for you to get started with AngularJS is to create the tradtional
"Hello World!" app:
1. In your favorite text editor, create an HTML file
@@ -27,13 +27,13 @@ Now let's take a closer look at that code, and see what is going on behind
the scenes.
The first line of interest defines the `ng` namespace, which makes
`angular` work across all browsers (especially important for IE):
AngularJS work across all browsers (especially important for IE):
<pre>
<html xmlns:ng="http://angularjs.org">
</pre>
The next line downloads the `angular` script, and instructs `angular` to process
The next line downloads the angular script, and instructs angular to process
the entire HTML page when it is loaded:
<pre>
@@ -41,7 +41,7 @@ the entire HTML page when it is loaded:
ng:autobind></script>
</pre>
(For details on what happens when `angular` processes an HTML page,
(For details on what happens when angular processes an HTML page,
see {@link guide/dev_guide.bootstrap Bootstrap}.)
Finally, this line in the `<body>` of the page is the template that describes
@@ -54,16 +54,16 @@ how to display our greeting in the UI:
Note the use of the double curly brace markup (`{{ }}`) to bind the expression to
the greeting text. Here the expression is the string literal 'World'.
Next let's look at a more interesting example, that uses `angular` to
Next let's look at a more interesting example, that uses AngularJS to
bind a dynamic expression to our greeting text.
# Hello <angular/> World!
# Hello AngularJS World!
This example demonstrates `angular`'s two-way data binding:
This example demonstrates angular's two-way data binding:
1. Edit the HTML file you created in the "Hello World!" example above.
2. Replace the contents of `<body>` with the code from the __Source__ box below.
3. Refresh your browswer window.
3. Refresh your browser window.
<doc:example>
<doc:source>
@@ -86,7 +86,7 @@ called `yourname`.
* You did not need to explicitly register an event listener or define an event handler for events!
Now try typing your name into the input box, and notice the immediate change to
the displayed greeting. This demonstrates the concept of `angular`'s
the displayed greeting. This demonstrates the concept of angular's
{@link guide/dev_guide.templates.databinding bi-directional data binding}. Any changes to the input
field are immediately
reflected in the model (one direction), and any changes to the model are
@@ -135,8 +135,11 @@ In addition, angular comes with a set of Services, which have the following prop
# Where To Go Next
* For explanations and examples of the angular concepts presented on this page, see the {@link
guide/index Developer Guide}.
* If you like what you've learned so far, you should definitely check out our awesome {@link
tutorial/ Tutorial}, which walk you through the process of building real apps with AngularJS.
* For additional hands-on examples of using `angular`, including more source code that you can
copy and paste into your own pages, take a look through the `angular` {@link cookbook/ Cookbook}.
* For further explanations and examples of the AngularJS concepts presented on this page, see the
{@link guide/index Developer Guide}.
* For additional hands-on examples of using AngularJS, including more source code that you can
copy and paste into your own pages, take a look through the {@link cookbook/ Cookbook}.
+79 -80
View File
@@ -2,40 +2,40 @@
@name Tutorial
@description
A great way to get introduced to angular is to work through this tutorial, which walks you through
the construction of an angular web app. The app you will build is a catalog that displays a list of
Android devices, lets you filter the list to see only devices that interest you, and then view
A great way to get introduced to Angular is to work through this tutorial, which walks you through
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
of Android devices, lets you filter the list to see only devices that interest you, and then view
details for any device.
<img src="img/tutorial/catalog_screen.png">
As you work through this tutorial, you will learn how angular makes browsers smarter — without the
use of extensions or plugins.
Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions
or plug-ins. As you work through the tutorial, you will:
* You will see examples of how to use client-side data binding and dependency injection to build
dynamic views of data that change immediately in response to user actions.
* You will see how angular creates listeners on your data without the need for DOM manipulation.
* You will learn a better, easier way to test your web apps.
* You will learn how to use angular services to make common web tasks, such as getting data into
your app, easier.
* See examples of how to use client-side data binding and dependency injection to build dynamic
views of data that change immediately in response to user actions.
* See how Angular creates listeners on your data without the need for DOM manipulation.
* Learn a better, easier way to test your web apps.
* Learn how to use Angular services to make common web tasks, such as getting data into your app,
easier.
And all of this works in any browser without modifications!
And all of this works in any browser without modification to the browser!
When you finish the tutorial you will be able to:
* Create a dynamic application that works in any browser
* Define the differences between angular and common JavaScript frameworks
* Understand how data binding works in angular
* Use the angular-seed project to quickly boot-strap your own projects
* Create and run tests
* Identify resources for learning more about angular
* Create a dynamic application that works in any browser.
* Define the differences between Angular and common JavaScript frameworks.
* Understand how data binding works in AngularJS.
* Use the angular-seed project to quickly boot-strap your own projects.
* Create and run tests.
* Identify resources for learning more about AngularJS.
The tutorial is will guide you through the process of building a simple application, including
writing and running unit and end-to-end tests, and will allow you to experiment with angular and
the application through experiments suggested at the end of each step.
The tutorial guides you through the entire process of building a simple application, including
writing and running unit and end-to-end tests. Experiments at the end of each step provide
suggestions for you learn more about AngularJS and the application you are building.
You can go through the whole tutorial in a couple of hours or you may want to spend a pleasant day
really digging into it. If you're looking for a shorter introduction to angular, check out the
really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
{@link misc/started Getting Started} document.
@@ -46,11 +46,11 @@ really digging into it. If you're looking for a shorter introduction to angular,
# Working with the code
There are two ways that you can you follow this tutorial and hack on the code, both available on
Mac/Linux or Windows environment. The first work flow uses Git versioning system for source code
management, the second work flow doesn't depend on any source control system and instead uses
scripts to copy snapshots of project files into your workspace (`sandbox`) directory. Choose the
one you prefer:
You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
environment. Options for working with the tutorial are to use the Git versioning system for source
code management or to use scripts that copy snapshots of project files into your workspace
(`sandbox`) directory. Select one of the tabs below and follow the instructions for setting up your
computer for your preferred option.
<doc:tutorial-instructions show="true">
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
@@ -59,67 +59,68 @@ one you prefer:
following command in a terminal window:</p>
<pre><code>java -version</code></pre>
<p>You will need Java to run unit tests.</p></li>
<li><p>Get Git from <a href="http://git-scm.com/download">here</a></p>
<p>You can build it from source or use pre-compiled package.</p></li>
<li><p>Download Git from the <a href="http://git-scm.com/download">Git</a> site.</p>
<p>You can build Git from source or use the pre-compiled package.</p></li>
<li><p>Clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running this command:</p>
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
<p>This will create <code>angular-phonecat</code> directory in current directory.</p></li>
<p>This command creates the <code>angular-phonecat</code> directory in your current
directory.</p></li>
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
<pre><code>cd angular-phonecat</code></pre>
<p>The tutorial instructions assume you are running all commands from this directory.</p></li>
<li><p>You'll also need an http server running on your system. Mac and Linux machines
typically have Apache preinstalled.</p>
<p>If you don't already have an http server installed, you can <a
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p></li>
<li><p>You will need an http server running on your system. Mac and Linux machines typically
have Apache pre-installed, but If you don't already have one installed, you can <a
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
node.js</a> and use it to run <code>scripts/web-server.js</code> a simple bundled http
server.</p></li>
node.js</a>. Use <code>node</code> to run <code>scripts/web-server.js</code>, a simple bundled
http server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="git-win" title="Git on Windows">
<ol>
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed and that the
<code>java</code> executable is on your <code>PATH</code> by running this command in windows
command line:</p>
<li><p>You will need Java to run unit tests, so run the following command to verify that you
have <a href="http://java.com/">Java</a> installed and that the <code>java</code> executable is on
your <code>PATH</code>.</p>
<pre><code>java -version</code></pre>
<p>You will need Java to run unit tests.</p></li>
<li><p>Install msysGit from <a href="http://git-scm.com/download">here</a></p></li>
<p></p></li>
<li><p>Install msysGit from <a href="http://git-scm.com/download">the Git</a> site.</p></li>
<li><p>Open msysGit bash and clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running this command:</p>
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
<p>This will create angular-phonecat directory in your current directory.</p></li>
<li><p>Change your current directory to angular-phonecat:</p>
<p>This command creates the angular-phonecat directory in your current directory.</p></li>
<li><p>Change your current directory to angular-phonecat.</p>
<pre><code>cd angular-phonecat</code></pre>
<p>The tutorial instructions assume you are running all commands from this directory.</p>
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p>
<p>You should run all <code>git</code> commands from msysGit bash.</p>
<p>Other commands like <code>test-server.bat</code> or <code>test.bat</code> that will be
introduced soon, should be executed from the windows command line.</li>
<li><p>You'll also need an http server running on your system.</p>
<p>If you don't already have an http server installed, you can install <a
href="http://nodejs.org/">node.js</a>. Just download <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them and add
<code>nodejs\bin</code> into your <code>PATH</code> and use <code>node</code> to run
<code>scripts\web-server.js</code> — a simple bundled http server.</p></li>
<p>Other commands like <code>test-server.bat</code> or <code>test.bat</code> should be
executed from the Windows command line.</li>
<li><p>You need an http server running on your system. If you don't already have one
installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
<code>scripts\web-server.js</code>, a simple, bundled http server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
<ol>
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed by running the
following command in a terminal window:</p>
<li><p>You need Java to run unit tests, so verify that you have <a
href="http://java.com/">Java</a> installed by running the following command in a terminal
window:</p>
<pre><code>java -version</code></pre>
<p>You will need Java to run unit tests.</p></li>
<li><p>Download the <a href="http://code.angularjs.org/angular-phonecat/">zip archive</a>
with all files and unzip them into [tutorial-dir] directory</p></li>
<li><p>Change your current directory to [tutorial-dir]/sanbox:</p>
<li><p>Download the <a href="http://code.angularjs.org/angular-phonecat/">zip archive</a>
containing all of the files and unzip them into the [tutorial-dir] directory</p>.</li>
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
<p>The tutorial instructions assume you are running all commands from this directory.</p></li>
<li><p>You'll also need an http server running on your system. Mac and Linux machines
typically have Apache preinstalled.</p>
<p>If you don't already have an http server installed, you can <a
<p>The tutorial instructions assume you are running all commands from your
<code>sandbox</code> directory.</p></li>
<li><p>You need an http server running on your system and Mac and Linux machines typically
have Apache pre-installed. If you don't have an http server installed, you can <a
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
node.js</a> and use it to run <code>scripts/web-server.js</code> a simple bundled http
node.js</a> and use it to run <code>scripts/web-server.js</code>, a simple bundled http
server.</p></li>
</ol>
</doc:tutorial-instruction>
@@ -127,25 +128,23 @@ server.</p></li>
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
<ol>
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed and that the
<code>java</code> executable is on your <code>PATH</code> by running this command in windows
command line:</p>
<code>java</code> executable is on your <code>PATH</code> by running the following command in the
Windows command line:</p>
<pre><code>java -version</code></pre>
<p>You will need Java to run unit tests.</p></li>
<li><p>Download the <a href="http://code.angularjs.org/angular-phonecat/">zip archive</a>
with all files and unzip them into [tutorial-dir] directory</p></li>
<li><p>Change your current directory to [tutorial-dir]/sanbox:</p>
<p>You need Java to run unit tests, so download the <a
href="http://code.angularjs.org/angular-phonecat/">zip archive</a> that contains all of the files
and unzip the files into the [tutorial-dir] directory</p></li>
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
<p>The tutorial instructions assume you are running all commands from this directory.</p></li>
<li><p>You'll also need an http server running on your system.</p>
<p>If you don't already have an http server installed, you can install <a
href="http://nodejs.org/">node.js</a>. Just download <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them and add
<code>nodejs\bin</code> into your <code>PATH</code> and use <code>node</code> to run
<code>scripts\web-server.js</code> — a simple bundled http server.</p></li>
<li><p>You need an http server running on your system, but if you don't already have one
already installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
<code>scripts\web-server.js</code>, a simple bundled http server.</p></li>
</ol>
</doc:tutorial-instruction>
</doc:tutorial-instructions>
For either work flow you'll also need a web browser and your favorite text editor.
Let's get going with {@link step_00 step 0}.
The last thing to do is to make sure your computer has a web browser and a good text editor
installed. Now, let's get going with {@link step_00 step 0}.
+13 -13
View File
@@ -5,8 +5,8 @@
<ul doc:tutorial-nav="0"></ul>
You are now ready to build the phonecat application. In this step, you will become familiar with
the most important source code files, learn how to start the development servers bundled with
You are now ready to build the Angular phonecat application. In this step, you will become familiar
with the most important source code files, learn how to start the development servers bundled with
angular-seed, and run the application in the browser.
@@ -78,7 +78,7 @@ directory.</li>
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
<ol>
<li><p>In angular-phonecat directory, run this command:</p>
<li><p>In the angular-phonecat directory, run this command:</p>
<pre><code>./goto_step.sh 0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
@@ -110,7 +110,7 @@ href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
<ol>
<li><p>Open windows command line and run this command (in angular-phonecat directory):</p>
<li><p>Open windows command line and run this command (in the angular-phonecat directory):</p>
<pre><code>goto_step.bat 0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
@@ -144,7 +144,7 @@ href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html
You can now see the page in your browser. It's not very exciting, but that's OK.
The static HTML page that displays "Nothing here yet!" was constructed with the HTML code shown
below. The code contains some key angular elements that we will need going forward.
below. The code contains some key Angular elements that we will need going forward.
__`app/index.html`:__
<pre>
@@ -172,25 +172,25 @@ __`app/index.html`:__
<html xmlns:ng="http://angularjs.org">
This `xmlns` declaration for the `ng` namespace must be specified in all angular applications in
order to make angular work with XHTML and IE versions older than 9 (regardless of whether you are
This `xmlns` declaration for the `ng` namespace must be specified in all Angular applications in
order to make Angular work with XHTML and IE versions older than 9 (regardless of whether you are
using XHTML or HTML).
* angular script tag
* Angular script tag
<script src="lib/angular/angular.js" ng:autobind>
This single line of code is all that is needed to bootstrap an angular application.
The code downloads the `angular.js` script and registers a callback that will be executed by the
browser when the containing HTML page is fully downloaded. When the callback is executed, angular
looks for the {@link api/angular.directive.ng:autobind ng:autobind} attribute. If angular finds
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
looks for the {@link api/angular.directive.ng:autobind ng:autobind} attribute. If Angular finds
`ng:autobind`, it creates a root scope for the application and associates it with the `<html>`
element of the template:
<img src="img/tutorial/tutorial_00_final.png">
As you will see shortly, everything in angular is evaluated within a scope. We'll learn more
As you will see shortly, everything in Angular is evaluated within a scope. We'll learn more
about this in the next steps.
@@ -198,7 +198,7 @@ about this in the next steps.
Most of the files in your working directory come from the {@link
https://github.com/angular/angular-seed angular-seed project} which is typically used to bootstrap
new angular projects. The seed project includes the latest angular libraries, test libraries,
new Angular projects. The seed project includes the latest Angular libraries, test libraries,
scripts and a simple example app, all pre-configured for developing a typical web app.
For the purposes of this tutorial, we modified the angular-seed with the following changes:
@@ -210,7 +210,7 @@ For the purposes of this tutorial, we modified the angular-seed with the followi
# Summary
Now let's go to step 1 and add some content to the web app.
Now let's go to {@link step_01 step 1} and add some content to the web app.
<ul doc:tutorial-nav="0"></ul>
+2 -2
View File
@@ -50,8 +50,8 @@ __`app/index.html`:__
# Summary
This addition to your app uses static HTML to display the list. Now, let's go to step 2 to learn
how to use angular to dynamically generate the same list.
This addition to your app uses static HTML to display the list. Now, let's go to {@link step_02
step 2} to learn how to use angular to dynamically generate the same list.
<ul doc:tutorial-nav="1"></ul>
+44 -38
View File
@@ -1,23 +1,23 @@
@ngdoc overview
@name Tutorial: 2 - Angular Template
@name Tutorial: 2 - Angular Templates
@description
<ul doc:tutorial-nav="2"></ul>
Now it's time to make this web page dynamic with angular. We'll also add a test that verifies the
Now it's time to make the web page dynamic -- with Angular. We'll also add a test that verifies the
code for the controller we are going to add.
There are many ways to structure the code for an application. With angular, we encourage the use of
{@link http://en.wikipedia.org/wiki/ModelViewController the MVC design pattern} to decouple the
code and separate concerns. With that in mind, let's use a little angular and JavaScript to add
model, view, and controller components to our app.
There are many ways to structure the code for an application. For Angular apps, we encourage the
use of {@link http://en.wikipedia.org/wiki/ModelViewController the Model-View-Controller (MVC)
design pattern} to decouple the code and to separate concerns. With that in mind, let's use a
little Angular and JavaScript to add model, view, and controller components to our app.
<doc:tutorial-instructions step="2"></doc:tutorial-instructions>
The app now contains a list with 3 phones.
The app now contains a list with three phones.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}:
@@ -25,7 +25,7 @@ https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}:
## Template for the View
The __view__ component is constructed by angular from this template:
The __view__ component is constructed by Angular from this template:
__`app/index.html`:__
<pre>
@@ -46,22 +46,24 @@ __`app/index.html`:__
</pre>
We replaced the hard-coded phone list with the {@link api/angular.widget.@ng:repeat ng:repeat
widget} and two {@link guide/dev_guide.expressions angular expressions} enclosed in curly braces:
widget} and two {@link guide/dev_guide.expressions Angular expressions} enclosed in curly braces:
`{{phone.name}}` and `{{phone.snippet}}`:
* The `ng:repeat="phone in phones"` statement in the `<li>` tag is an angular repeater. It tells
angular to create a `<li>` element for each phone in the phones list, using the first `<li>` tag as
the template.
* The `ng:repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
tag as the template.
<img src="img/tutorial/tutorial_02_final.png">
* The curly braces around `phone.name` and `phone.snippet` are an example of {@link
guide/dev_guide.compiler.markup angular markup}. The curly markup is shorthand for the angular
directive {@link api/angular.directive.ng:bind ng:bind}. The `ng:bind` directives indicate to
angular that these are template binding points. Binding points are locations in the template where
angular creates data-binding between the view and the model. In angular, the view is a projection
of the model through the HTML template. This means that whenever the model changes, angular
refreshes the appropriate binding points, which updates the view.
* The curly braces around `phone.name` and `phone.snippet` are examples of {@link
guide/dev_guide.compiler.markup Angular markup}. The curly markup is shorthand for the Angular
directive {@link api/angular.directive.ng:bind ng:bind}. An `ng:bind` directive indicates a
template binding point to Angular. Binding points are locations in a template where Angular creates
data-binding between the view and the model.
In Angular, the view is a projection of the model through the HTML template. This means that
whenever the model changes, Angular refreshes the appropriate binding points, which updates the
view.
## Model and Controller
@@ -86,26 +88,27 @@ function PhoneListCtrl() {
Although the controller is not yet doing very much controlling, it is playing a crucial role. By
providing context for our data model, the controller allows us to establish data-binding between
the model and the view. Note in the following how we connected the dots between our presentation,
data, and logic components:
the model and the view. We connected the dots between the presentation, data, and logic components
as follows:
* The name of our controller function (in the JavaScript file `controllers.js`) matches the {@link
api/angular.directive.ng:controller ng:controller} directive in the `<body>` tag (`PhoneListCtrl`).
* We instantiated our data within the scope of our controller function, and our template binding
* The data is instantiated within the *scope* of our controller function; our template binding
points are located within the block bounded by the `<body ng:controller="PhoneListCtrl">` tag.
Angular scopes are a crucial concept in angular; you can think of scopes as the glue that makes the
template, model and controller all work together. Angular uses scopes, along with the information
contained in the template, data model, and controller, to keep the model and view separated but in
sync. Any changes to the model are reflected in the view; any changes that occur in the view are
reflected in the model. To learn more about angular scopes, see the {@link api/angular.scope
angular scope documentation}.
The concept of a scope in Angular is crucial; a scope can be seen as the glue which allows the
template, model and controller to work together. Angular uses scopes, along with the information
contained in the template, data model, and controller, to keep models and views separate, but in
sync. Any changes made to the model are reflected in the view; any changes that occur in the view
are reflected in the model.
To learn more about Angular scopes, see the {@link api/angular.scope angular scope documentation}.
## Tests
The "Angular way" makes it easy for us to test as we develop; the unit test for your newly created
controller looks as follows:
The "Angular way" makes it easy to test code as it is being developed. Take a look at the following
unit test for your newly created controller:
__`test/unit/controllersSpec.js`:__
<pre>
@@ -121,14 +124,16 @@ describe('PhoneCat controllers', function() {
});
</pre>
Ease of testing is another cornerstone of angular's design philosophy. All we are doing here is
showing how easy it is to create a unit test. The test verifies that we have 3 records in the
phones array.
The test verifies that we have three records in the phones array and the example demonstrates how
easy it is to create a unit test for code in Angular. Since testing is such a critical part of
software development, we make it easy to create tests in Angular so that developers are encouraged
to write them.
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Jasmine is not required by angular, we used it to write all tests in this
tutorial. You can learn about Jasmine on the {@link http://pivotal.github.com/jasmine/ Jasmine home
page} and on the {@link https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the {@link
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
The angular-seed project is pre-configured to run all unit tests using {@link
http://code.google.com/p/js-test-driver/ JsTestDriver}. To run the test, do the following:
@@ -189,8 +194,9 @@ and kill the script, then repeat the procedure above.
# Summary
You now have a dynamic app that features separate model, view, and controller components, and
you're testing as you go. Now, let's go to step 3 to learn how to add full text search to the app.
You now have a dynamic app that features separate model, view, and controller components, and you
are testing as you go. Now, let's go to {@link step_03 step 3} to learn how to add full text search
to the app.
<ul doc:tutorial-nav="2"></ul>
+32 -24
View File
@@ -6,19 +6,19 @@
We did a lot of work in laying a foundation for the app in the last step, so now we'll do something
simple, and add full text search (yes, it will be simple!). We will also write an end-to-end test,
because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it, and
quickly detects regressions.
simple; we will add full text search (yes, it will be simple!). We will also write an end-to-end
test, because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it,
and quickly detects regressions.
<doc:tutorial-instructions step="3"></doc:tutorial-instructions>
The app now has a search box. The phone list on the page changes depending on what a user types
into the search box.
The app now has a search box. Notice that the phone list on the page changes depending on what a
user types into the search box.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-2...step-3
The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3
GitHub}:
@@ -43,13 +43,13 @@ __`app/index.html`:__
...
</pre>
We added a standard HTML `<input>` tag and use angular's {@link api/angular.Array.filter $filter}
We added a standard HTML `<input>` tag and used angular's {@link api/angular.Array.filter $filter}
function to process the input for the `ng:repeater`.
This lets a user enter search criteria and immediately see the effects of their search on the phone
list. This new code demonstrates the following:
list. This new code demonstrates the following:
* Data-binding. This is one of the core features in angular. When the page loads, angular binds the
* Data-binding. This is one of the core features in Angular. When the page loads, Angular binds the
name of the input box to a variable of the same name in the data model and keeps the two in sync.
In this code, the data that a user types into the input box (named __`query`__) is immediately
@@ -59,7 +59,7 @@ the DOM to reflect the current state of the model.
<img src="img/tutorial/tutorial_03_final.png">
* Use of `$filter`. The {@link api/angular.Array.filter $filter} method, uses the `query` value, to
* Use of `$filter`. The {@link api/angular.Array.filter $filter} method uses the `query` value to
create a new array that contains only those records that match the `query`.
`ng:repeat` automatically updates the view in response to the changing number of phones returned
@@ -67,7 +67,7 @@ by the `$filter`. The process is completely transparent to the developer.
## Test
In step 2, we learned how to write and run unit tests. Unit tests are perfect for testing
In Step 2, we learned how to write and run unit tests. Unit tests are perfect for testing
controllers and other components of our application written in JavaScript, but they can't easily
test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much
better choice.
@@ -101,9 +101,9 @@ describe('PhoneCat App', function() {
Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the end-to-end test uses APIs of {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
angular's end-to-end test runner}.
Angular's end-to-end test runner}.
To run the end-to-end test, open the following in a new browser tab:
To run the end-to-end test, open one of the following in a new browser tab:
* node.js users: {@link http://localhost:8000/test/e2e/runner.html}
* users with other http servers:
@@ -111,7 +111,7 @@ To run the end-to-end test, open the following in a new browser tab:
* casual reader: {@link http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html}
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in angular. Although this example is for a simple test, it
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.
# Experiments
@@ -138,6 +138,14 @@ and title elements:
Be sure to *remove* the `ng:controller` declaration from the body element.
While using double curlies works fine in within the title element, you might have noticed that
for a split second they are actually displayed to the user while the page is loading. A better
solution would be to use the {@link api/angular.directive.ng:bind ng:bind} or {@link
api/angular.directive.ng:bind-template ng:bind-template} directives, which are invisible to the
user while the page is loading:
<title ng:bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
<pre>
@@ -154,20 +162,20 @@ and title elements:
});
</pre>
Refresh the browser tab with end-to-end test runner to see the test fail. Now add a `div` or `p`
element with `id` `"status"` and content with the `query` binding into the `index.html` template to
make the test pass.
Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content
with the `query` binding.
* Add a `pause()` statement into an end-to-end test and rerun it. You'll see the runner pausing,
giving you the opportunity to explore the state of your application displayed in the browser. The
app is live! Change the search query to prove it. This is great for troubleshooting end-to-end
tests.
* Add a `pause()` statement into an end-to-end test and rerun it. You'll see the runner pause; this
gives you the opportunity to explore the state of your application while it is displayed in the
browser. The app is live! You can change the search query to prove it. Notice how useful this is
for troubleshooting end-to-end tests.
# Summary
With full text search under our belt and a test to verify it, let's go to step 4 to learn how to
add sorting capability to the phone app.
We have now added full text search and included a test to verify that search works! Now let's go on
to {@link step_04 step 4} to learn how to add sorting capability to the phone app.
<ul doc:tutorial-nav="3"></ul>
+7 -8
View File
@@ -16,9 +16,8 @@ the repeater, and letting the data binding magic do the rest of the work.
You should see that in addition to the search box, the app displays a drop down menu that allows
users to control the order in which the phones are listed.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-3...step-4
GitHub}:
The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 GitHub}:
## Template
@@ -48,7 +47,7 @@ __`app/index.html`:__
...
</pre>
In the `index.html` template we made the following changes:
We made the following changes to the `index.html` template:
* First, we added a `<select>` html element named `orderProp`, so that our users can pick from the
two provided sorting options.
@@ -63,7 +62,7 @@ Angular creates a two way data-binding between the select element and the `order
`orderProp` is then used as the input for the `$orderBy` method.
As we discussed in the section about data-binding and the repeater in step 3, whenever the model
changes (for example because a user changes the order with the select drop down menu), angular's
changes (for example because a user changes the order with the select drop down menu), Angular's
data-binding will cause the view to automatically update. No bloated DOM manipulation code is
necessary!
@@ -179,7 +178,7 @@ The end-to-end test verifies that the ordering mechanism of the select box is wo
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html
angular's server}.
Angular's server}.
# Experiments
@@ -192,8 +191,8 @@ text.
# Summary
Now that you have added list sorting and tested the app, go to step 5 to learn about angular
services and how angular uses dependency injection.
Now that you have added list sorting and tested the app, go to {@link step_05 step 5} to learn
about Angular services and how Angular uses dependency injection.
<ul doc:tutorial-nav="4"></ul>
+2 -2
View File
@@ -209,8 +209,8 @@ to the first 5 in the list. Use the following code in the xhr callback:
# Summary
Now that you have learned how easy it is to use angular services (thanks to angular's
implementation of dependency injection), go to step 6, where you will add some thumbnail images of
phones and some links.
implementation of dependency injection), go to {@link step_06 step 6}, where you will add some
thumbnail images of phones and some links.
<ul doc:tutorial-nav="5"></ul>
+4 -4
View File
@@ -58,11 +58,11 @@ __`app/index.html`:__
To dynamically generate links that will in the future lead to phone detail pages, we used the
now-familiar {@link guide/dev_guide.compiler.markup double-curly brace markup} in the `href`
attribute values. In step 2, we added the `{{phone.name}}` binding as the element content. In this
step the '{{phone.id}}' binding is used in the element attribute.
step the `{{phone.id}}` binding is used in the element attribute.
We also added phone images next to each record using an image tag with the {@link
api/angular.directive.ng:src ng:src} directive. That directive prevents the browser from treating
the angular `{{ exppression }}` markup literally, which it would have done if we had only specified
the angular `{{ expression }}` markup literally, which it would have done if we had only specified
an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). Using
`ng:src` prevents the browser from making an http request to an invalid location.
@@ -98,8 +98,8 @@ making an extraneous request to `/app/%7B%7Bphone.imageUrl%7D%7D` (or
# Summary
Now that you have added phone images and links, go to step 7 to learn about angular layout
templates and how angular makes it easy to create applications that have multiple views.
Now that you have added phone images and links, go to {@link step_07 step 7} to learn about angular
layout templates and how angular makes it easy to create applications that have multiple views.
<ul doc:tutorial-nav="6"></ul>
+5 -5
View File
@@ -68,8 +68,8 @@ function PhoneCatCtrl($route) {
We created a new controller called `PhoneCatCtrl`. We declared its dependency on the `$route`
service and used this service to declare that our application consists of two different views:
* The phone list view will be shown when the URL hash fragment is `/phone`. To construct this view,
angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
* The phone list view will be shown when the URL hash fragment is `/phones`. To construct this
view, angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
* The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
`:phoneId` is a variable part of the URL. To construct the phone details view, angular will use the
@@ -151,7 +151,7 @@ __`app/partials/phone-list.html`:__
TBD: detail view for {{params.phoneId}}
</pre>
Note how we are using `params` model defined in the `PhoneCanCtrl` controller.
Note how we are using `params` model defined in the `PhoneCatCtrl` controller.
## Test
@@ -202,8 +202,8 @@ inheritance and model property shadowing do some wonders.
# Summary
With the routing set up and the phone list view implemented, we're ready to go to step 8 to
implement the phone details view.
With the routing set up and the phone list view implemented, we're ready to go to {@link step_08
step 8} to implement the phone details view.
<ul doc:tutorial-nav="7"></ul>
+2 -2
View File
@@ -181,8 +181,8 @@ Nexus S details page.
# Summary
Now that the phone details view is in place, proceed to step 9 to learn how to write your own
custom display filter.
Now that the phone details view is in place, proceed to {@link step_09 step 9} to learn how to
write your own custom display filter.
<ul doc:tutorial-nav="8"></ul>
+2 -2
View File
@@ -114,8 +114,8 @@ the following to index.html:
# Summary
Now that you have learned how to write and test a custom filter, go to step 10 to learn how we can
use angular to enhance the phone details page further.
Now that you have learned how to write and test a custom filter, go to {@link step_10 step 10} to
learn how we can use angular to enhance the phone details page further.
<ul doc:tutorial-nav="9"></ul>
+2 -2
View File
@@ -133,8 +133,8 @@ template remains operational.
# Summary
With the phone image swapper in place, we're ready for step 11 (the last step!) to learn an even
better way to fetch data.
With the phone image swapper in place, we're ready for {@link step_11 step 11} (the last step!) to
learn an even better way to fetch data.
<ul doc:tutorial-nav="10"></ul>
+8 -7
View File
@@ -67,17 +67,17 @@ __`app/js/controllers.js`.__
<pre>
...
function PhoneListCtrl(Phone_) {
function PhoneListCtrl(Phone) {
this.orderProp = 'age';
this.phones = Phone_.query();
this.phones = Phone.query();
}
//PhoneListCtrl.$inject = ['Phone'];
function PhoneDetailCtrl(Phone_) {
function PhoneDetailCtrl(Phone) {
var self = this;
self.phone = Phone_.get({phoneId: self.params.phoneId}, function(phone) {
self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) {
self.mainImageUrl = phone.images[0];
});
@@ -94,7 +94,7 @@ Notice how in `PhoneListCtrl` we replaced:
with:
this.phones = Phone_.query();
this.phones = Phone.query();
This is a simple statement that we want to query for all phones.
@@ -116,7 +116,7 @@ We have modified our unit tests to verify that our new service is issuing HTTP r
processing them as expected. The tests also check that our controllers are interacting with the
service correctly.
The {@link api/angular.service.$resource $resource} client augments the response object with
The {@link api/angular.service.$resource $resource} service augments the response object with
methods for updating and deleting the resource. If we were to use the standard `toEqual` matcher,
our tests would fail because the test values would not match the responses exactly. To solve the
problem, we use a newly-defined `toEqualData` {@link
@@ -201,7 +201,8 @@ output.
# Summary
There you have it! We have created a web app in a relatively short amount of time.
There you have it! We have created a web app in a relatively short amount of time. In the {@link
the_end closing notes} we'll cover were to go from here.
<ul doc:tutorial-nav="11"></ul>
+2
View File
@@ -0,0 +1,2 @@
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />
+3
View File
@@ -0,0 +1,3 @@
controller: {{name}}<br />
Book Id: {{prams.bookId}}<br />
Chapter Id: {{params.chapterId}}
+1
View File
@@ -0,0 +1 @@
Content of template1.html
+1
View File
@@ -0,0 +1 @@
Content of template2.html
+32 -4
View File
@@ -89,6 +89,14 @@ describe('ngdoc', function(){
'<pre class="doc-source">\n&lt;&gt;\n</pre></doc:example><p>after</p>');
});
it('should preserve the jsfiddle attribute', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:source jsfiddle="foo">lala</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source" jsfiddle="foo">lala</pre></doc:example><p>after</p>');
});
it('should escape <doc:scenario> element', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
@@ -316,8 +324,8 @@ describe('ngdoc', function(){
expect(doc.requires).toEqual([
{name:'$service', text:'<p>for \n<code>A</code></p>'},
{name:'$another', text:'<p>for <code>B</code></p>'}]);
expect(doc.html()).toContain('<a href="#!angular.service.$service">$service</a>');
expect(doc.html()).toContain('<a href="#!angular.service.$another">$another</a>');
expect(doc.html()).toContain('<a href="#!/api/angular.service.$service">$service</a>');
expect(doc.html()).toContain('<a href="#!/api/angular.service.$another">$another</a>');
expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
expect(doc.html()).toContain('<p>for <code>B</code></p>');
});
@@ -461,8 +469,8 @@ describe('ngdoc', function(){
});
});
describe('@depricated', function() {
it('should parse @depricated', function() {
describe('@deprecated', function() {
it('should parse @deprecated', function() {
var doc = new Doc('@deprecated Replaced with foo.');
doc.parse();
expect(doc.deprecated).toBe('Replaced with foo.');
@@ -496,6 +504,26 @@ describe('ngdoc', function(){
});
describe('function', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'function',
name:'some.name',
param: [
{name:'a', optional: true},
{name:'b', type: 'someType', optional: true, 'default': '"xxx"'},
{name:'c', type: 'string', description: 'param desc'}
],
returns: {type: 'number', description: 'return desc'}
});
doc.html_usage_function(dom);
expect(dom).toContain('some.name([a][, b], c)'); //TODO(i) the comma position here is lame
expect(dom).toContain('param desc');
expect(dom).toContain('(optional="xxx")');
expect(dom).toContain('return desc');
});
});
describe('filter', function(){
it('should format', function(){
var doc = new Doc({
+1 -1
View File
@@ -12,7 +12,7 @@ describe('sitemap', function(){
});
it('should render ngdoc url', function(){
var map = new SiteMap([new Doc({section: 'foo', name: 'a.b.c<>\'"&'})]);
var map = new SiteMap([new Doc({section: 'foo', id: 'a.b.c<>\'"&'})]);
expect(map.render()).toContain([
' <url>',
'<loc>http://docs.angularjs.org/#!/foo/a.b.c&lt;&gt;&apos;&quot;&amp;</loc>',
+76
View File
@@ -0,0 +1,76 @@
/**
* Generate appCache Manifest file here
*/
exports.appCache = appCache;
var fs = require('q-fs');
var Q = require('qq');
function identity($) {return $;}
function appCache(path) {
if(!path) {
return appCacheTemplate();
}
var blackList = ["build/docs/offline.html",
"build/docs/sitemap.xml",
"build/docs/robots.txt",
"build/docs/docs-scenario.html",
"build/docs/docs-scenario.js",
"build/docs/appcache.manifest",
"build/docs/.htaccess"
];
var result = ["CACHE MANIFEST",
"# " + new Date().toISOString(),
"",
"# cache all of these",
"CACHE:",
"../angular.min.js"];
var resultPostfix = ["",
"FALLBACK:",
"/offline.html",
"",
"# allow access to google analytics and twitter when we are online",
"NETWORK:",
"*"];
var promise = fs.listTree(path).then(function(files){
var fileFutures = [];
files.forEach(function(file){
fileFutures.push(fs.isFile(file).then(function(isFile){
if (isFile && blackList.indexOf(file) == -1) {
return file.replace('build/docs/','');
}
}));
});
return Q.deep(fileFutures);
}).then(function(files){
return result.concat(files.filter(identity)).concat(resultPostfix).join('\n');
});
return promise;
}
function appCacheTemplate() {
return ["CACHE MANIFEST",
"# " + new Date().toISOString(),
"",
"# cache all of these",
"CACHE:",
"syntaxhighlighter/syntaxhighlighter-combined.js",
"../angular.min.js",
"docs-combined.js",
"docs-keywords.js",
"docs-combined.css",
"syntaxhighlighter/syntaxhighlighter-combined.css",
"img/texture_1.png",
"img/yellow_bkgnd.jpg",
"",
"FALLBACK:",
"/ offline.html",
"",
"# allow access to google analytics and twitter when we are online",
"NETWORK:",
"*"].join('\n');
}
-69
View File
@@ -1,69 +0,0 @@
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;
+74 -77
View File
@@ -3,88 +3,85 @@ 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;
SiteMap = require('SiteMap.js').SiteMap,
appCache = require('appCache.js').appCache,
Q = require('qq');
var docs = [];
var start;
var work = callback.chain(function(){
start = now();
process.on('uncaughtException', function (err) {
console.error(err.stack || err);
});
var start = now();
var docs;
writer.makeDir('build/docs/syntaxhighlighter').then(function() {
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(){
return reader.collect();
}).then(function generateHtmlDocPartials(docs_) {
docs = docs_;
ngdoc.merge(docs);
var fileFutures = [];
docs.forEach(function(doc){
writer.output(doc.section + '/' + doc.id + '.html', doc.html(), writes.waitFor());
fileFutures.push(writer.output(doc.section + '/' + doc.id + '.html', doc.html()));
});
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('examples', writes.waitFor());
writer.copyTpl('index.html', writes.waitFor());
writer.copyTpl('offline.html', writes.waitFor());
writer.output('app-cache.manifest',
appCacheTemplate().replace(/%TIMESTAMP%/, (new Date()).toISOString()),
writes.waitFor());
writer.merge(['docs.js',
'doc_widgets.js'],
'docs-combined.js',
writes.waitFor());
writer.merge(['docs.css',
'doc_widgets.css'],
'docs-combined.css',
writes.waitFor());
writer.copyTpl('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.merge(['syntaxhighlighter/shCore.js',
'syntaxhighlighter/shBrushJScript.js',
'syntaxhighlighter/shBrushXml.js'],
'syntaxhighlighter/syntaxhighlighter-combined.js',
writes.waitFor());
writer.merge(['syntaxhighlighter/shCore.css',
'syntaxhighlighter/shThemeDefault.css'],
'syntaxhighlighter/syntaxhighlighter-combined.css',
writes.waitFor());
writer.copyTpl('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);
///////////////////////////////////
writeTheRest(fileFutures);
return Q.deep(fileFutures);
}).then(function generateManifestFile() {
return appCache('build/docs/').then(function(list) {
writer.output('appcache-offline.manifest',list)
});
}).then(function printStats() {
console.log('DONE. Generated ' + docs.length + ' pages in ' + (now()-start) + 'ms.' );
}).end();
function writeTheRest(writesFuture) {
var metadata = ngdoc.metadata(docs);
writesFuture.push(writer.copyDir('img'));
writesFuture.push(writer.copyDir('examples'));
writesFuture.push(writer.copyTpl('index.html'));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html',
'<!-- jquery place holder -->', '<script src=\"jquery.min.js\"><\/script>'));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-nocache.html',
'manifest="appcache.manifest"', ''));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-nocache.html',
'manifest="appcache.manifest"', '',
'<!-- jquery place holder -->', '<script src=\"jquery.min.js\"><\/script>'));
writesFuture.push(writer.copyTpl('offline.html'));
writesFuture.push(writer.copyTpl('docs-scenario.html'));
writesFuture.push(writer.copyTpl('jquery.min.js'));
writesFuture.push(writer.output('docs-keywords.js',
['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';']));
writesFuture.push(writer.output('sitemap.xml', new SiteMap(docs).render()));
writesFuture.push(writer.output('docs-scenario.js', ngdoc.scenarios(docs)));
writesFuture.push(writer.output('robots.txt', 'Sitemap: http://docs.angularjs.org/sitemap.xml\n'));
writesFuture.push(writer.output('appcache.manifest',appCache()));
writesFuture.push(writer.copyTpl('.htaccess'));
writesFuture.push(writer.merge(['docs.js',
'doc_widgets.js'],
'docs-combined.js'));
writesFuture.push(writer.merge(['docs.css',
'doc_widgets.css'],
'docs-combined.css'));
writesFuture.push(writer.merge(['syntaxhighlighter/shCore.js',
'syntaxhighlighter/shBrushJScript.js',
'syntaxhighlighter/shBrushXml.js'],
'syntaxhighlighter/syntaxhighlighter-combined.js'));
writesFuture.push(writer.merge(['syntaxhighlighter/shCore.css',
'syntaxhighlighter/shThemeDefault.css'],
'syntaxhighlighter/syntaxhighlighter-combined.css'));
}
function now(){ return new Date().getTime(); }
function appCacheTemplate() {
return ["CACHE MANIFEST",
"# %TIMESTAMP%",
"",
"# cache all of these",
"CACHE:",
"jquery.min.js",
"syntaxhighlighter/syntaxhighlighter-combined.js",
"../angular.min.js",
"docs-combined.js",
"docs-keywords.js",
"docs-combined.css",
"syntaxhighlighter/syntaxhighlighter-combined.css",
"img/texture_1.png",
"img/yellow_bkgnd.jpg",
"",
"FALLBACK:",
"/ offline.html",
"",
"# allow access to google analytics and twitter when we are online",
"NETWORK:",
"*"].join('\n');
}
function noop(){};
+37 -19
View File
@@ -111,9 +111,11 @@ Doc.prototype = {
'</pre></div>';
});
} else if (isDocWidget('example')) {
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
function(_, before, content, after){
return '<pre class="doc-source">' + htmlEscape(content) + '</pre>';
text = text.replace(/<doc:source(\s+jsfiddle="[^"]+")?>([\s\S]*)<\/doc:source>/mi,
function(_, jsfiddle, content){
return '<pre class="doc-source"' + (jsfiddle || '') +'>' +
htmlEscape(content) +
'</pre>';
});
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
function(_, before, content, after){
@@ -236,7 +238,7 @@ Doc.prototype = {
}
dom.h('Dependencies', self.requires, function(require){
dom.tag('code', function(){
dom.tag('a', {href:"#!angular.service." + require.name}, require.name);
dom.tag('a', {href:"#!/api/angular.service." + require.name}, require.name);
});
dom.html(require.text);
});
@@ -309,7 +311,7 @@ Doc.prototype = {
var self = this;
dom.h('Usage', function(){
dom.code(function(){
dom.text(self.name);
dom.text(self.name.split('service.').pop());
dom.text('(');
self.parameters(dom, ', ');
dom.text(');');
@@ -511,6 +513,9 @@ Doc.prototype = {
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description);
method.html_usage_parameters(dom);
self.html_usage_this(dom);
method.html_usage_returns(dom);
dom.h('Example', method.example, dom.html);
});
});
@@ -543,22 +548,35 @@ Doc.prototype = {
//////////////////////////////////////////////////////////
function scenarios(docs){
var specs = [];
docs.forEach(function(doc){
specs.push('describe("' + doc.section + '/' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
specs.push(' browser().navigateTo("index.html#!/' + doc.section + '/' + doc.id + '");');
specs.push(' });');
specs.push('');
doc.scenarios.forEach(function(scenario){
specs.push(indent(trim(scenario), 2));
specs.push('describe("angular+jqlite", function() {');
appendSpecs('index-nocache.html');
specs.push('});');
specs.push('');
specs.push('');
specs.push('describe("angular+jquery", function() {');
appendSpecs('index-jq-nocache.html');
specs.push('});');
return specs.join('\n');
function appendSpecs(htmlFile) {
docs.forEach(function(doc){
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
specs.push(' browser().navigateTo("' + htmlFile + '#!/' + doc.section + '/' + doc.id + '");');
specs.push(' });');
specs.push(' ');
doc.scenarios.forEach(function(scenario){
specs.push(indent(trim(scenario), 4));
specs.push('');
});
specs.push('});');
specs.push('');
});
specs.push('});');
specs.push('');
if (doc.scenario) {
}
});
return specs.join('\n');
}
}
+85 -85
View File
@@ -2,98 +2,98 @@
* All reading related code here. This is so that we can separate the async code from sync code
* for testability
*/
exports.collect = collect;
require.paths.push(__dirname);
var fs = require('fs'),
callback = require('callback');
var ngdoc = require('ngdoc.js'),
Q = require('qq'),
qfs = require('q-fs');
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('@section api\n' + doc, file, line);
}));
}));
findNgDocInDir('docs/content', callback.waitMany(callback));
callback.done();
}
function collect() {
var allDocs = [];
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){
fs.stat(directory + '/' + file, docNotify.waitFor(function(err, stats){
if (err) return this.error(err);
if (stats.isFile()) {
if (!file.match(/\.ngdoc$/)) return;
console.log('reading', directory + '/' + file, '...');
fs.readFile(directory + '/' + file, docNotify.waitFor(function(err, content){
if (err) return this.error(err);
var section = '@section ' + directory.split('/').pop() + '\n';
docNotify(section + content.toString(), directory + '/' +file, 1);
}));
} else if(stats.isDirectory()) {
findNgDocInDir(directory + '/' + file, docNotify.waitFor(docNotify));
}
}));
});
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?/, ''));
//collect docs in JS Files
var path = 'src';
var promiseA = Q.when(qfs.listTree(path), function(files) {
var done;
//read all files in parallel.
files.forEach(function(file) {
var work;
if(/\.js$/.test(file)) {
console.log("reading " + file + ".......");
work = Q.when(qfs.read(file, 'b'), function(content) {
processJsFile(content, file).forEach (function(doc) {
allDocs.push(doc);
});
});
}
done = Q.when(done, function() {
return work;
});
});
callback.done();
}));
return done;
});
//collect all NG Docs in Content Folder
var path2 = 'docs/content';
var promiseB = Q.when(qfs.listTree(path2), function(files){
var done2;
files.forEach(function(file) {
var work2;
if (file.match(/\.ngdoc$/)) {
console.log("reading " + file + ".......");
work2 = Q.when(qfs.read(file, 'b'), function(content){
var section = '@section ' + file.split('/')[2] + '\n';
allDocs.push(new ngdoc.Doc(section + content.toString(),file, 1).parse());
});
}
done2 = Q.when(done2, function() {
return work2;
});
});
return done2;
});
return Q.join(promiseA, promiseB, function() {
return allDocs;
});
}
function processJsFile(content, file) {
var docs = [];
var lines = content.toString().split(NEW_LINE);
var text;
var startingLine ;
var match;
var inDoc = false;
exports.collect = collect;
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/)){
//console.log(file, startingLine)
docs.push(new ngdoc.Doc('@section api\n' + text, file, startingLine).parse());
}
doc = null;
inDoc = false;
}
// is the comment add text
if (inDoc){
text.push(line.replace(/^\s*\*\s?/, ''));
}
});
return docs;
}
+11
View File
@@ -0,0 +1,11 @@
## OFFLINE SUPPORT ##
# These rules tell apache to check if there is a cookie called "offline", with value set to the
# current angular version. If this rule matches the appcache-offline.manifest will be served for
# requests to appcache.manifest
#
# This file must be processed by Rake in order to replace %ANGULAR_VERSION% with the actual version.
RewriteEngine on
RewriteCond %{HTTP_COOKIE} ng-offline="NG_VERSION_FULL"
RewriteRule appcache.manifest appcache-offline.manifest
+31 -6
View File
@@ -1,9 +1,3 @@
@namespace doc url("http://docs.angularjs.org/");
doc\:example {
display: none;
}
ul.doc-example {
list-style-type: none;
position: relative;
@@ -25,6 +19,37 @@ ul.doc-example > li.doc-example-heading {
margin-bottom: -10px;
}
span.nojsfiddle {
float: right;
font-size: 14px;
margin-right:10px;
margin-top: 10px;
}
form.jsfiddle {
position: absolute;
right: 0;
z-index: 1;
height: 14px;
}
form.jsfiddle button {
cursor: pointer;
padding: 4px 10px;
margin: 10px;
background-color: #FFF;
font-weight: bold;
color: #7989D6;
border-color: #7989D6;
-moz-border-radius: 8px;
-webkit-border-radius:8px;
border-radius: 8px;
}
form.jsfiddle textarea, form.jsfiddle input {
display: none;
}
li.doc-example-live {
padding: 10px;
font-size: 1.2em;
+57 -12
View File
@@ -23,18 +23,21 @@
angular.widget('doc:example', function(element){
this.descend(true); //compile the example code
element.hide();
var example = element.find('pre.doc-source').eq(0),
//jQuery find() methods in this widget contain primitive selectors on purpose so that we can use
//jqlite instead. jqlite's find() method currently supports onlt getElementsByTagName!
var example = element.find('pre').eq(0), //doc-source
exampleSrc = example.text(),
scenario = element.find('pre.doc-scenario').eq(0);
jsfiddle = example.attr('jsfiddle') || true,
scenario = element.find('pre').eq(1); //doc-scenario
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: [' +
jsFiddleButton(jsfiddle) + // may or may not have value
'<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>';
@@ -47,18 +50,61 @@
'</ul>';
var tabs = angular.element(tabHtml);
tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html));
tabs.find('li').eq(1).find('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);
if (window.execScript) { // IE
window.execScript(script || '"stupid IE!"'); // IE complains when evaling empty string
} else {
window.eval(script);
}
} catch (e) {
alert(e);
}
function jsFiddleButton(jsfiddle) {
if (jsfiddle !== 'false') {
if(jsfiddle == true) {
//dynamically generate a fiddle
var fiddleUrl = 'http://jsfiddle.net/api/post/library/pure/',
fiddleSrc = exampleSrc,
stripIndent = fiddleSrc.match(/^(\s*)/)[1].length;
//escape closing textarea
fiddleSrc = fiddleSrc.replace(/<\/textarea>/gi,'&lt;/textarea&gt;')
//strip extra indentation
fiddleSrc = fiddleSrc.replace(new RegExp('^\\s{' + stripIndent + '}', 'gm'), '');
return '<form class="jsfiddle" method="post" action="' + fiddleUrl + '" target="_blank">' +
'<textarea name="css">' +
'body { font-family: Arial,Helvetica,sans-serif; }\n' +
'body, td, th { font-size: 14px; margin: 0; }\n' +
'table { border-collapse: separate; border-spacing: 2px; display: table; margin-bottom: 0; margin-top: 0; -moz-box-sizing: border-box; text-indent: 0; }\n' +
'a:link, a:visited, a:hover { color: #5D6DB6; text-decoration: none; }\n' +
'</textarea>' +
'<input type="text" name="title" value="AngularJS Live Example">' +
'<textarea name="html">' +
'<script src="' + angularJsUrl + '" ng:autobind></script>\n\n' +
'<!-- AngularJS Example Code: -->\n\n' +
fiddleSrc +
'</textarea>' +
'<button>edit at jsFiddle</button>' +
'</form>';
} else {
//use existing fiddle
fiddleUrl = "http://jsfiddle.net" + jsfiddle;
return '<form class="jsfiddle" method="get" action="' + fiddleUrl + '" target="_blank">' +
'<button>edit at jsFiddle</button>' +
'</form>';
}
}
return '';
}
});
function indent(text) {
@@ -86,7 +132,7 @@
};
var HTML_TPL =
'<p><a ng:init="showInstructions = {show}" ng:show="!showInstructions" ng:click="showInstructions = true" href>Workspace Reset Instructions</a></p>' +
'<p><a ng:init="showInstructions = {show}" ng:show="!showInstructions" ng:click="showInstructions = true" href>Workspace Reset Instructions &nbsp;&#x27A4;</a></p>' +
'<div ng:controller="TutorialInstructionsCtrl" ng:show="showInstructions">' +
'<div class="tabs-nav">' +
'<ul>' +
@@ -140,12 +186,12 @@
'</div>';
angular.widget('doc:tutorial-instructions', function(element) {
element.hide();
this.descend(true);
var tabs = angular.element(HTML_TPL.replace('{show}', element.attr('show') || 'false')),
nav = tabs.find('.tabs-nav ul'),
content = tabs.find('.tabs-content-inner'),
nav = tabs.find('ul'),
// use simple selectors because jqLite find() supports getElementsByTagName only
content = tabs.find('div').find('div'),
children = element.children();
if (children.length) {
@@ -165,7 +211,6 @@
element.html('');
element.append(tabs);
element.show();
});
+114 -5
View File
@@ -29,6 +29,10 @@ p {
line-height: 1.4em;
}
li > p {
padding-left: 0;
}
h2 {
margin: 1.5em 0 1em 0;
}
@@ -76,10 +80,26 @@ li {
#footer {
clear: both;
padding: 2em 4em 1em;
text-align: right;
font-size: 12px;
}
#offline{
text-decoration:underline;
cursor:pointer;
color:blue;
}
#copyright {
float:right;
text-align: right;
}
#version {
margin-top: -1.2em;
margin-left: -2em;
margin-right: 0.5em;
color: #251BE0;
}
/*----- navigation styles -----*/
@@ -102,6 +122,7 @@ li {
#navbar a:link, #navbar a:visited {
font-size: 1.2em;
color: #FFF;
font-weight: bold;
text-decoration: none;
text-align: center;
display: inline-block;
@@ -117,7 +138,6 @@ li {
#navbar a.current {
font-weight: bold;
background-color: #333;
-webkit-border-radius:10px;
@@ -165,10 +185,20 @@ li {
}
#sidebar #content-list.tutorial {
list-style: none;
text-indent: -1.2em;
}
#sidebar #content-list.tutorial .level-0 {
text-indent: 0;
}
#content-list {
background: #fff;
padding: 1em 0.4em 1em 2em;
margin: 0.95em -1em -1em -0.6em;
margin: 0.95em -0.95em -1em -0.6em;
line-height: 1.5em;
}
@@ -190,6 +220,14 @@ li {
margin-left: 1em;
}
#content-list .level-4 {
margin-left: 2em;
}
#content-list .level-5 {
margin-left: 3em;
}
#content-list a.current {
font-weight: bold;
@@ -251,7 +289,8 @@ li {
}
#content img {
#content img:not(.ng-directive) {
/* the negation rule above is to avoid applying this rule to images in buzz and other examples */
display: block;
margin: 2em auto;
padding: 1em;
@@ -267,7 +306,7 @@ li {
}
#content > div > pre {
#content pre {
padding-left: 1.5em;
}
@@ -285,3 +324,73 @@ li {
display: inline;
padding-left: 0;
}
/* subpages */
#fader {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.8;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
z-index: 3;
}
#subpage > div {
position: fixed;
top: 50%;
left: 50%;
width: 729px;
margin-top: -140px;
margin-left: -365px;
z-index: 3;
background-color: #7989D6;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: 4px 4px 6px #48577D;
-webkit-box-shadow: 4px 4px 6px #48577D;
-moz-box-shadow: 4px 4px 6px #48577D;
}
#subpage h2 {
height: 1.8em;
-moz-border-radius-topright: 15px;
-moz-border-radius-topleft: 15px;
border-radius-topright: 15px;
border-radius-topleft: 15px;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
border-top-right-radius: 15px;
border-top-left-radius: 15px;
padding: 0.6em 0 0 1em;
margin: 0;
color: white;
}
#subpage > div > a {
color: black;
float: right;
margin: -40px 10px;
font-size: 1em;
}
#subpage > div > a:hover {
text-decoration: none;
color: white;
}
#subpage > div > p {
background-color: white;
padding: 0.5em 1em 0.5em 1em;
margin-bottom: 0.5em;
}
#cacheButton {
margin: 0em 2em 1em 0em;
float:right;
}
+32 -4
View File
@@ -1,10 +1,18 @@
var HAS_HASH = /#/;
DocsController.$inject = ['$location', '$browser', '$window'];
function DocsController($location, $browser, $window) {
DocsController.$inject = ['$location', '$browser', '$window', '$cookies'];
function DocsController($location, $browser, $window, $cookies) {
window.$root = this.$root;
var self = this;
var self = this,
OFFLINE_COOKIE_NAME = 'ng-offline',
HAS_HASH = /#/;
this.$location = $location;
self.versionNumber = angular.version.full;
self.version = angular.version.full + " " + angular.version.codeName;
self.subpage = false;
self.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
if (!HAS_HASH.test($location.href)) {
$location.hashPath = '!/api';
}
@@ -68,6 +76,26 @@ function DocsController($location, $browser, $window) {
"body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
};
/** stores a cookie that is used by apache to decide which manifest ot send */
this.enableOffline = function() {
//The cookie will be good for one year!
var date = new Date();
date.setTime(date.getTime()+(365*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
var value = angular.version.full;
document.cookie = OFFLINE_COOKIE_NAME + "="+value+expires+"; path=" + $location.path;
//force the page to reload so server can serve new manifest file
window.location.reload(true);
};
// bind escape to hash reset callback
angular.element(window).bind('keydown', function(e) {
if (e.keyCode === 27) {
self.subpage = false;
self.$eval();
}
});
}
// prevent compilation of code
+35 -9
View File
@@ -2,10 +2,10 @@
<html xmlns:ng="http://angularjs.org/"
xmlns:doc="http://docs.angularjs.org/"
ng:controller="DocsController"
manifest="app-cache.manifest">
manifest="appcache.manifest">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title ng:bind-template="&lt;angular/&gt;: {{partialTitle}}">&lt;angular/&gt;</title>
<title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
<meta name="fragment" content="!">
<link rel="stylesheet" href="docs-combined.css" type="text/css"/>
<link rel="stylesheet" href="syntaxhighlighter/syntaxhighlighter-combined.css" type="text/css"/>
@@ -44,19 +44,19 @@
<![endif]-->
<ul id="navbar">
<li><a href="http://angularjs.org/">&lt;angular/&gt;</a></li>
<li><a href="#!/guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
<li><a href="#!/api" ng:class="selectedSection('api')">API Reference</a></li>
<li><a href="#!/cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
<li><a href="http://angularjs.org/">AngularJS</a></li>
<li><a href="#!/misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
<li><a href="#!/tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
<li><a href="#!/api" ng:class="selectedSection('api')">API Reference</a></li>
<li><a href="#!/cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
<li><a href="#!/guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
</ul>
<div id="sidebar">
<input type="text" name="search" id="search-box" placeholder="search the docs"
tabindex="1" accesskey="s">
<ul id="content-list">
<ul id="content-list" ng:class="sectionId">
<li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)">
<a href="{{getUrl(page)}}" ng:class="selectedPartial(page)"
ng:bind="page.shortName"
@@ -72,11 +72,37 @@
<ng:include id="content" src="getCurrentPartial()" onload="afterPartialLoaded()"></ng:include>
</div>
<div id="footer">© 2010-2011 angular</div>
<div id="footer">
<a id="version"
ng:href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
ng:bind-template="v{{version}}">
</a>
<a ng:hide="offlineEnabled" ng:click ="subpage = true">(enable offline support)</a>
<span ng:show="offlineEnabled">(offline support enabled)</span>
<span id="copyright">© 2010-2011 AngularJS</span>
</div>
</div>
<div id="fader" ng:show="subpage" style="display: none"></div>
<div id="subpage" ng:show="subpage" style="display: none">
<div>
<h2>Would you like full offline support for this AngularJS Docs App?</h2>
<a ng:click="subpage=false">&#10005;</a>
<p>
If you want to be able to access the entire AngularJS documentation offline, click the
button below. This will reload the current page and trigger background downloads of all the
necessary files (approximately 3.5MB). The next time you load the docs, the browser will
use these cached files.
<br><br>
This feature is supported on all modern browsers, except for IE9 which lacks application
cache support.
</p>
<button id="cacheButton" ng:click="enableOffline()">Let me have them all!</button>
</div>
</div>
<script src="jquery.min.js"></script>
<script src="syntaxhighlighter/syntaxhighlighter-combined.js"></script>
<!-- jquery place holder -->
<script src="../angular.min.js" ng:autobind></script>
<script src="docs-combined.js"></script>
<script src="docs-keywords.js"></script>
+105 -72
View File
@@ -3,37 +3,126 @@
* for testability
*/
require.paths.push(__dirname);
var fs = require('fs');
var qfs = require('q-fs');
var Q = require('qq');
var OUTPUT_DIR = "build/docs/";
var fs = require('fs');
function output(docs, content, callback){
callback();
exports.output = function(file, content){
console.log('writing ', file);
var fullPath = OUTPUT_DIR + file;
var dir = parent(fullPath);
return Q.when(exports.makeDir(dir), function(error) {
qfs.write(fullPath,exports.toString(content));
});
}
//recursively create directory
exports.makeDir = function (path) {
var parts = path.split(/\//);
var path = ".";
//Sequentially create directories
var done = Q.defer();
(function createPart() {
if(!parts.length) {
done.resolve();
} else {
path += "/" + parts.shift();
qfs.isDirectory(path).then(function(isDir) {
if(!isDir) {
qfs.makeDirectory(path);
}
createPart();
});
}
})();
return done.promise;
};
exports.copyTpl = function(filename) {
return exports.copy('docs/src/templates/' + filename, OUTPUT_DIR + filename);
};
exports.copy = function (from, to) {
var args = [].slice.call(arguments);
args.shift(); // drop 'from'
args.shift(); // drop 'to'
// Have to use rb (read binary), char 'r' is infered by library.
return qfs.read(from,'b').then(function(content) {
var replacementKey,
replacement;
while (args.length) {
replacementKey = args.shift();
replacement = args.shift();
if(replacementKey != undefined && replacement != undefined) {
content = content.toString().replace(replacementKey, replacement);
}
}
qfs.write(to, content);
});
}
exports.copyDir = function copyDir(dir) {
return qfs.listDirectoryTree('docs/' + dir).then(function(dirs) {
var done;
dirs.forEach(function(dirToMake) {
done = Q.when(done, function() {
return exports.makeDir("./build/" + dirToMake);
});
});
return done;
}).then(function() {
return qfs.listTree('docs/' + dir);
}).then(function(files) {
files.forEach( function(file) {
exports.copy(file,'./build/' + file);
});
});
};
exports.merge = function(srcs, to) {
return merge(srcs.map(function(src) { return 'docs/src/templates/' + src; }), OUTPUT_DIR + to);
};
function merge(srcs, to) {
var contents = [];
//Sequentially read file
var done;
srcs.forEach(function (src) {
done = Q.when(done, function(content) {
if(content) contents.push(content);
return qfs.read(src, 'b');
});
});
// write to file
return Q.when(done, function(content) {
contents.push(content);
qfs.write(to, contents.join('\n'));
});
}
//----------------------- Synchronous Methods ----------------------------------
function parent(file) {
var parts = file.split('/');
parts.pop();
return parts.join('/');
}
exports.output = function(file, content, callback){
console.log('write', file);
exports.makeDir(parent(OUTPUT_DIR + file), callback.waitFor(function(){
fs.writeFile(
OUTPUT_DIR + file,
exports.toString(content),
callback);
}));
};
exports.toString = function toString(obj){
exports.toString = function toString(obj) {
switch (typeof obj) {
case 'string':
return obj;
case 'object':
if (obj instanceof Array) {
obj.forEach(function (value, key){
obj.forEach(function (value, key) {
obj[key] = toString(value);
});
return obj.join('');
@@ -44,61 +133,5 @@ exports.toString = function toString(obj){
return obj;
};
exports.makeDir = function (path, callback) {
var parts = path.split(/\//);
path = '.';
(function next(error){
if (error && error.code != 'EEXIST') return callback.error(error);
if (parts.length) {
path += '/' + parts.shift();
fs.mkdir(path, 0777, next);
} else {
callback();
}
})();
};
exports.copyTpl = 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 copyDir(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){
var path = 'docs/' + dir + '/' + file;
fs.stat(path, callback.waitFor(function(err, stat) {
if (err) return this.error(err);
if (stat.isDirectory()) {
copyDir(dir + '/' + file, callback.waitFor());
} else {
copy(path, OUTPUT_DIR + '/' + dir + '/' + file, callback.waitFor());
}
}));
});
callback();
}));
}));
};
exports.merge = function(srcs, to, callback){
merge(srcs.map(function(src) { return 'docs/src/templates/' + src; }), OUTPUT_DIR + to, callback);
};
function merge(srcs, to, callback) {
var content = [];
srcs.forEach(function (src) {
content.push(fs.readFileSync(src));
});
fs.writeFile(to, content.join('\n'), callback.waitFor());
}
function noop(){};
+34 -2
View File
@@ -7,17 +7,49 @@ load:
- test/jquery_remove.js
- src/Angular.js
- src/JSON.js
- src/*.js
- src/service/*.js
- src/Compiler.js
- src/Scope.js
- src/Injector.js
- src/parser.js
- src/Resource.js
- src/Browser.js
- src/sanitizer.js
- src/jqLite.js
- src/apis.js
- src/filters.js
- src/formatters.js
- src/validators.js
- src/service/cookieStore.js
- src/service/cookies.js
- src/service/defer.js
- src/service/document.js
- src/service/exceptionHandler.js
- src/service/hover.js
- src/service/invalidWidgets.js
- src/service/location.js
- src/service/log.js
- src/service/resource.js
- src/service/route.js
- src/service/updateView.js
- src/service/window.js
- src/service/xhr.bulk.js
- src/service/xhr.cache.js
- src/service/xhr.error.js
- src/service/xhr.js
- src/directives.js
- src/markups.js
- src/widgets.js
- example/personalLog/*.js
- test/testabilityPatch.js
- src/scenario/Scenario.js
- src/scenario/output/*.js
- src/jstd-scenario-adapter/*.js
- src/scenario/*.js
- src/angular-mocks.js
- test/mocks.js
- test/scenario/*.js
- test/scenario/output/*.js
- test/jstd-scenario-adapter/*.js
- test/*.js
- test/service/*.js
- example/personalLog/test/*.js
+34 -2
View File
@@ -7,17 +7,49 @@ load:
- test/jquery_alias.js
- src/Angular.js
- src/JSON.js
- src/*.js
- src/service/*.js
- src/Compiler.js
- src/Scope.js
- src/Injector.js
- src/parser.js
- src/Resource.js
- src/Browser.js
- src/sanitizer.js
- src/jqLite.js
- src/apis.js
- src/filters.js
- src/formatters.js
- src/validators.js
- src/service/cookieStore.js
- src/service/cookies.js
- src/service/defer.js
- src/service/document.js
- src/service/exceptionHandler.js
- src/service/hover.js
- src/service/invalidWidgets.js
- src/service/location.js
- src/service/log.js
- src/service/resource.js
- src/service/route.js
- src/service/updateView.js
- src/service/window.js
- src/service/xhr.bulk.js
- src/service/xhr.cache.js
- src/service/xhr.error.js
- src/service/xhr.js
- src/directives.js
- src/markups.js
- src/widgets.js
- example/personalLog/*.js
- test/testabilityPatch.js
- src/scenario/Scenario.js
- src/scenario/output/*.js
- src/jstd-scenario-adapter/*.js
- src/scenario/*.js
- src/angular-mocks.js
- test/mocks.js
- test/scenario/*.js
- test/scenario/output/*.js
- test/jstd-scenario-adapter/*.js
- test/*.js
- test/service/*.js
- example/personalLog/test/*.js
+32 -2
View File
@@ -5,8 +5,38 @@ load:
- lib/jasmine-jstd-adapter/JasmineAdapter.js
- src/Angular.js
- src/JSON.js
- src/*.js
- src/service/*.js
- src/Compiler.js
- src/Scope.js
- src/Injector.js
- src/parser.js
- src/Resource.js
- src/Browser.js
- src/sanitizer.js
- src/jqLite.js
- src/apis.js
- src/filters.js
- src/formatters.js
- src/validators.js
- src/service/cookieStore.js
- src/service/cookies.js
- src/service/defer.js
- src/service/document.js
- src/service/exceptionHandler.js
- src/service/hover.js
- src/service/invalidWidgets.js
- src/service/location.js
- src/service/log.js
- src/service/resource.js
- src/service/route.js
- src/service/updateView.js
- src/service/window.js
- src/service/xhr.bulk.js
- src/service/xhr.cache.js
- src/service/xhr.error.js
- src/service/xhr.js
- src/directives.js
- src/markups.js
- src/widgets.js
- src/angular-mocks.js
- perf/data/*.js
- perf/testUtils.js
+32 -4
View File
@@ -7,8 +7,38 @@ load:
- test/jquery_remove.js
- src/Angular.js
- src/JSON.js
- src/*.js
- src/service/*.js
- src/Compiler.js
- src/Scope.js
- src/Injector.js
- src/parser.js
- src/Resource.js
- src/Browser.js
- src/sanitizer.js
- src/jqLite.js
- src/apis.js
- src/filters.js
- src/formatters.js
- src/validators.js
- src/service/cookieStore.js
- src/service/cookies.js
- src/service/defer.js
- src/service/document.js
- src/service/exceptionHandler.js
- src/service/hover.js
- src/service/invalidWidgets.js
- src/service/location.js
- src/service/log.js
- src/service/resource.js
- src/service/route.js
- src/service/updateView.js
- src/service/window.js
- src/service/xhr.bulk.js
- src/service/xhr.cache.js
- src/service/xhr.error.js
- src/service/xhr.js
- src/directives.js
- src/markups.js
- src/widgets.js
- example/personalLog/*.js
- test/testabilityPatch.js
- src/scenario/Scenario.js
@@ -26,8 +56,6 @@ load:
exclude:
- test/jquery_alias.js
- src/angular.prefix
- src/angular.suffix
- src/angular-bootstrap.js
- src/scenario/angular-bootstrap.js
- src/AngularPublic.js
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
20110322
20110615
+2 -1
View File
@@ -2200,7 +2200,8 @@ jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
// (i): disabled this log since its annoying
//this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
var latchFunctionResult;
try {
latchFunctionResult = this.latchFunction.apply(this.spec);
+28 -6
View File
@@ -1,3 +1,5 @@
'use strict';
////////////////////////////////////
if (typeof document.getAttribute == $undefined)
@@ -62,12 +64,10 @@ var _undefined = undefined,
$boolean = 'boolean',
$console = 'console',
$date = 'date',
$display = 'display',
$element = 'element',
$function = 'function',
$length = 'length',
$name = 'name',
$none = 'none',
$noop = 'noop',
$null = 'null',
$number = 'number',
@@ -115,7 +115,7 @@ var _undefined = undefined,
angularCallbacks = extensionMap(angular, 'callbacks'),
nodeName_,
rngScript = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/,
uid = ['0', '0', '0'];
uid = ['0', '0', '0'],
DATE_ISOSTRING_LN = 24;
/**
@@ -144,7 +144,7 @@ var _undefined = undefined,
* @param {Object|Array} obj Object to iterate over.
* @param {function()} iterator Iterator function.
* @param {Object} context Object to become context (`this`) for the iterator function.
* @returns {Objet|Array} Reference to `obj`.
* @returns {Object|Array} Reference to `obj`.
*/
function forEach(obj, iterator, context) {
var key;
@@ -874,7 +874,7 @@ function toKeyValue(obj) {
/**
* we need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
* We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
* segments:
* segment = *pchar
@@ -944,7 +944,7 @@ function angularInit(config, document){
if (config.css)
$browser.addCss(config.base_url + config.css);
else if(msie<8)
$browser.addJs(config.base_url + config.ie_compat, config.ie_compat_id);
$browser.addJs(config.ie_compat, config.ie_compat_id);
}
}
@@ -1002,3 +1002,25 @@ function assertArg(arg, name, reason) {
function assertArgFn(arg, name) {
assertArg(isFunction(arg, name, 'not a function'));
}
/**
* @ngdoc property
* @name angular.version
* @description
* Object which contains information about the current AngularJS version. The object has following
* properties:
*
* - `full` `{string}` full version string, e.g. "0.9.18"
* - `major` `{number}` major version number, e.g. 0
* - `minor` `{number}` minor version number, e.g. 9
* - `dot` `{number}` dot version number, e.g. 18
* - `codeName` `{string}` code name of the release, e.g. "jiggling-armfat"
*/
var version = {
full: '"NG_VERSION_FULL"', // all of these placeholder strings will be replaced by rake's
major: "NG_VERSION_MAJOR", // compile task
minor: "NG_VERSION_MINOR",
dot: "NG_VERSION_DOT",
codeName: '"NG_VERSION_CODENAME"'
};
+6 -10
View File
@@ -1,13 +1,7 @@
'use strict';
var browserSingleton;
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$browser
* @requires $log
*
* @description
* Represents the browser.
*/
angularService('$browser', function($log){
if (!browserSingleton) {
browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body),
@@ -17,6 +11,7 @@ angularService('$browser', function($log){
return browserSingleton;
}, {$inject:['$log']});
extend(angular, {
// disabled for now until we agree on public name
//'annotate': annotate,
@@ -39,7 +34,8 @@ extend(angular, {
'isFunction': isFunction,
'isObject': isObject,
'isNumber': isNumber,
'isArray': isArray
'isArray': isArray,
'version': version
});
//try to bind to jquery now so that one can write angular.element().read()
+137 -51
View File
@@ -1,3 +1,5 @@
'use strict';
//////////////////////////////
// Browser
//////////////////////////////
@@ -10,8 +12,9 @@ var XHR = window.XMLHttpRequest || function () {
/**
* @private
* @name Browser
* @ngdoc service
* @name angular.service.$browser
* @requires $log
*
* @description
* Constructor for the object exposed as $browser service.
@@ -21,6 +24,11 @@ var XHR = window.XMLHttpRequest || function () {
* - hide all the global state in the browser caused by the window object
* - abstract away all the browser specific features and inconsistencies
*
* For tests we provide {@link angular.mock.service.$browser mock implementation} of the `$browser`
* service, which can be used for convenient testing of the application without the interaction with
* the real browser apis.
*/
/**
* @param {object} window The global window object.
* @param {object} document jQuery wrapped document.
* @param {object} body jQuery wrapped document.body.
@@ -31,7 +39,10 @@ function Browser(window, document, body, XHR, $log) {
var self = this,
rawDocument = document[0],
location = window.location,
setTimeout = window.setTimeout;
setTimeout = window.setTimeout,
clearTimeout = window.clearTimeout,
pendingDeferIds = {},
lastLocationUrl;
self.isMock = false;
@@ -73,7 +84,9 @@ function Browser(window, document, body, XHR, $log) {
* @param {string} method Requested method (get|post|put|delete|head|json)
* @param {string} url Requested url
* @param {?string} post Post data to send (null if nothing to post)
* @param {function(number, string)} callback Function that will be called on response
* @param {function(number, string, function([string]))} callback Function that will be called on
* response. The third argument is a function that can be called to return a specified response
* header or an Object containing all headers (when called with no arguments).
* @param {object=} header additional HTTP headers to send with XHR.
* Standard headers are:
* <ul>
@@ -86,17 +99,24 @@ function Browser(window, document, body, XHR, $log) {
* Send ajax request
*/
self.xhr = function(method, url, post, callback, headers) {
var parsedHeaders;
outstandingRequestCount ++;
if (lowercase(method) == 'json') {
var callbackId = ("angular_" + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
var script = jqLite(rawDocument.createElement('script'))
.attr({type: 'text/javascript', src: url.replace('JSON_CALLBACK', callbackId)});
window[callbackId] = function(data){
window[callbackId] = undefined;
script.remove();
completeOutstandingRequest(callback, 200, data);
window[callbackId] = function(data) {
window[callbackId].data = data;
};
body.append(script);
var script = self.addJs(url.replace('JSON_CALLBACK', callbackId), null, function() {
if (window[callbackId].data) {
completeOutstandingRequest(callback, 200, window[callbackId].data);
} else {
completeOutstandingRequest(callback);
}
delete window[callbackId];
body[0].removeChild(script);
});
} else {
var xhr = new XHR();
xhr.open(method, url, true);
@@ -106,8 +126,35 @@ function Browser(window, document, body, XHR, $log) {
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
var status = xhr.status == 1223 ? 204 : xhr.status || 200;
completeOutstandingRequest(callback, status, xhr.responseText);
var status = xhr.status == 1223 ? 204 : xhr.status;
completeOutstandingRequest(callback, status, xhr.responseText, function(header) {
header = lowercase(header);
if (header) {
return parsedHeaders
? parsedHeaders[header] || null
: xhr.getResponseHeader(header);
} else {
// Return an object containing each response header
parsedHeaders = {};
forEach(xhr.getAllResponseHeaders().split('\n'), function(line) {
var i = line.indexOf(':'),
key = lowercase(trim(line.substr(0, i))),
value = trim(line.substr(i + 1));
if (parsedHeaders[key]) {
// Combine repeated headers
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
parsedHeaders[key] += ', ' + value;
} else {
parsedHeaders[key] = value;
}
});
return parsedHeaders;
}
});
}
};
xhr.send(post || '');
@@ -115,14 +162,17 @@ function Browser(window, document, body, XHR, $log) {
};
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#notifyWhenNoOutstandingRequests
* @methodOf angular.service.$browser
*
* @private
* Note: this method is used only by scenario runner
* TODO(vojta): prefix this method with $$ ?
* @param {function()} callback Function that will be called when no outstanding request
*/
self.notifyWhenNoOutstandingRequests = function(callback) {
// force browser to execute all pollFns - this is needed so that cookies and other pollers fire
// at some deterministic time in respect to the test runner's actions. Leaving things up to the
// regular poller would result in flaky tests.
forEach(pollFns, function(pollFn){ pollFn(); });
if (outstandingRequestCount === 0) {
callback();
} else {
@@ -136,16 +186,6 @@ function Browser(window, document, body, XHR, $log) {
var pollFns = [],
pollTimeout;
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#poll
* @methodOf angular.service.$browser
*/
self.poll = function() {
forEach(pollFns, function(pollFn){ pollFn(); });
};
/**
* @workInProgress
* @ngdoc method
@@ -161,17 +201,12 @@ function Browser(window, document, body, XHR, $log) {
* @returns {function()} the added function
*/
self.addPollFn = function(fn) {
if (!pollTimeout) self.startPoller(100, setTimeout);
if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
pollFns.push(fn);
return fn;
};
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#startPoller
* @methodOf angular.service.$browser
*
* @param {number} interval How often should browser call poll functions (ms)
* @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
*
@@ -179,9 +214,9 @@ function Browser(window, document, body, XHR, $log) {
* Configures the poller to run in the specified intervals, using the specified
* setTimeout fn and kicks it off.
*/
self.startPoller = function(interval, setTimeout) {
(function check(){
self.poll();
function startPoller(interval, setTimeout) {
(function check() {
forEach(pollFns, function(pollFn){ pollFn(); });
pollTimeout = setTimeout(check, interval);
})();
};
@@ -202,10 +237,13 @@ function Browser(window, document, body, XHR, $log) {
* Sets browser's url
*/
self.setUrl = function(url) {
var existingURL = location.href;
var existingURL = lastLocationUrl;
if (!existingURL.match(/#/)) existingURL += '#';
if (!url.match(/#/)) url += '#';
location.href = url;
if (existingURL != url) {
location.href = url;
}
};
/**
@@ -220,7 +258,7 @@ function Browser(window, document, body, XHR, $log) {
* @returns {string} Browser's url
*/
self.getUrl = function() {
return location.href;
return lastLocationUrl = location.href;
};
@@ -237,7 +275,7 @@ function Browser(window, document, body, XHR, $log) {
* The listener gets called with either HashChangeEvent object or simple object that also contains
* `oldURL` and `newURL` properties.
*
* NOTE: this api is intended for use only by the $location service. Please use the
* Note: this api is intended for use only by the $location service. Please use the
* {@link angular.service.$location $location service} to monitor hash changes in angular apps.
*
* @param {function(event)} listener Listener function to be called when url hash changes.
@@ -336,20 +374,49 @@ function Browser(window, document, body, XHR, $log) {
* @methodOf angular.service.$browser
* @param {function()} fn A function, who's execution should be defered.
* @param {number=} [delay=0] of milliseconds to defer the function execution.
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
*
* @description
* Executes a fn asynchroniously via `setTimeout(fn, delay)`.
*
* Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
* `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed via
* `$browser.defer.flush()`.
* `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
* via `$browser.defer.flush()`.
*
*/
self.defer = function(fn, delay) {
var timeoutId;
outstandingRequestCount++;
setTimeout(function() { completeOutstandingRequest(fn); }, delay || 0);
timeoutId = setTimeout(function() {
delete pendingDeferIds[timeoutId];
completeOutstandingRequest(fn);
}, delay || 0);
pendingDeferIds[timeoutId] = true;
return timeoutId;
};
/**
* THIS DOC IS NOT VISIBLE because ngdocs can't process docs for foo#method.method
*
* @name angular.service.$browser#defer.cancel
* @methodOf angular.service.$browser.defer
* @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
*
* @description
* Cancels a defered task identified with `deferId`.
*/
self.defer.cancel = function(deferId) {
if (pendingDeferIds[deferId]) {
delete pendingDeferIds[deferId];
clearTimeout(deferId);
completeOutstandingRequest(noop);
return true;
}
};
//////////////////////////////////////////////////////////////
// Misc API
//////////////////////////////////////////////////////////////
@@ -416,16 +483,35 @@ function Browser(window, document, body, XHR, $log) {
* @methodOf angular.service.$browser
*
* @param {string} url Url to js file
* @param {string=} dom_id Optional id for the script tag
* @param {string=} domId Optional id for the script tag
*
* @description
* Adds a script tag to the head.
*/
self.addJs = function(url, dom_id) {
var script = jqLite(rawDocument.createElement('script'));
script.attr('type', 'text/javascript');
script.attr('src', url);
if (dom_id) script.attr('id', dom_id);
body.append(script);
self.addJs = function(url, domId, done) {
// we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
//
// We need addJs to be able to add angular-ie-compat.js which is very special and must remain
// part of the DOM so that the embedded images can reference it. jQuery's append implementation
// (v1.4.2) fubars it.
var script = rawDocument.createElement('script');
script.type = 'text/javascript';
script.src = url;
if (domId) script.id = domId;
if (msie) {
script.onreadystatechange = function() {
/loaded|complete/.test(script.readyState) && done && done();
};
} else {
if (done) script.onload = script.onerror = done;
}
body[0].appendChild(script);
return script;
};
}
+13 -11
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Template provides directions an how to bind to a given element.
* It contains a list of init functions which need to be called to
@@ -87,7 +89,7 @@ Template.prototype = {
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
* {@link angular.markup markup}, {@link angular.attrMarkup attrMarkup},
* {@link angular.widget widgets}, and {@link angular.directive directives}. For each match it
* executes coresponding markup, attrMarkup, widget or directive template function and collects the
* executes corresponding markup, attrMarkup, widget or directive template function and collects the
* instance functions into a single template function which is then returned.
*
* The template function can then be used once to produce the view or as it is the case with
@@ -95,16 +97,16 @@ Template.prototype = {
* that is a DOM clone of the original template.
*
<pre>
//copile the entire window.document and give me the scope bound to this template.
var rootSscope = angular.compile(window.document)();
// compile the entire window.document and give me the scope bound to this template.
var rootScope = angular.compile(window.document)();
//compile a piece of html
var rootScope2 = angular.compile(''<div ng:click="clicked = true">click me</div>')();
// compile a piece of html
var rootScope2 = angular.compile('<div ng:click="clicked = true">click me</div>')();
//compile a piece of html and retain reference to both the dom and scope
// compile a piece of html and retain reference to both the dom and scope
var template = angular.element('<div ng:click="clicked = true">click me</div>'),
scoope = angular.compile(view)();
//at this point template was transformed into a view
scope = angular.compile(template)();
// at this point template was transformed into a view
</pre>
*
*
@@ -116,7 +118,7 @@ Template.prototype = {
* root scope is created.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
* cloned elements to the DOM document at the approriate place. The `cloneAttachFn` is
* cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
* called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
@@ -175,7 +177,7 @@ Template.prototype = {
* directives processing state. The compiler will process directives only when directives set to
* true.
*
* For information on how the compiler works, see the
* For information on how the compiler works, see the
* {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide.
*/
function Compiler(markup, attrMarkup, directives, widgets){
@@ -232,7 +234,7 @@ Compiler.prototype = {
* not a problem, but under some circumstances the values for data
* is not available until after the full view is computed. If such
* values are needed before they are computed the order of
* evaluation can be change using ng:eval-order
* evaluation can be changed using ng:eval-order
*
* @element ANY
* @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant
+8 -5
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @ngdoc function
* @name angular.injector
@@ -29,7 +31,7 @@
* * `self` - "`this`" to be used when invoking the function.
* * `fn` - the function to be invoked. The function may have the `$inject` property which
* lists the set of arguments which should be auto injected
* (see {@link guide.di dependency injection}).
* (see {@link guide/dev_guide.di dependency injection}).
* * `curryArgs(array)` - optional array of arguments to pass to function invocation after the
* injection arguments (also known as curry arguments or currying).
* * an `eager` property which is used to initialize the eager services.
@@ -60,7 +62,7 @@ function createInjector(factoryScope, factories, instanceCache) {
instanceCache[value] = invoke(factoryScope, factory);
}
return instanceCache[value];
};
}
function invoke(self, fn, args){
args = args || [];
@@ -73,8 +75,9 @@ function createInjector(factoryScope, factories, instanceCache) {
}
}
/*NOT_PUBLIC_YET
* @ngdoc function
/**
* THIS IS NOT PUBLIC DOC YET!
*
* @name angular.annotate
* @function
*
@@ -125,7 +128,7 @@ function angularServiceInject(name, fn, inject, eager) {
* extracting all arguments which and assuming that they are the
* injection names.
*/
var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/;
var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(.+?)\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
+2
View File
@@ -1,3 +1,5 @@
'use strict';
var array = [].constructor;
/**
+52 -28
View File
@@ -1,3 +1,5 @@
'use strict';
function Route(template, defaults) {
@@ -65,53 +67,64 @@ ResourceFactory.prototype = {
forEach(actions, function(action, name){
var isPostOrPut = action.method == 'POST' || action.method == 'PUT';
Resource[name] = function (a1, a2, a3) {
Resource[name] = function (a1, a2, a3, a4) {
var params = {};
var data;
var callback = noop;
var success = noop;
var error = null;
switch(arguments.length) {
case 3: callback = a3;
case 4:
error = a4;
success = a3;
//fallthrough
case 3:
case 2:
if (isFunction(a2)) {
callback = a2;
if (isFunction(a1)) {
success = a1;
error = a2;
break;
}
success = a2;
error = a3;
//fallthrough
} else {
params = a1;
data = a2;
success = a3;
break;
}
case 1:
if (isFunction(a1)) callback = a1;
if (isFunction(a1)) success = a1;
else if (isPostOrPut) data = a1;
else params = a1;
break;
case 0: break;
default:
throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments.";
throw "Expected between 0-4 arguments [params, data, success, error], got " +
arguments.length + " arguments.";
}
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
self.xhr(
action.method,
route.url(extend({}, action.params || {}, extractParams(data), params)),
route.url(extend({}, extractParams(data), action.params || {}, params)),
data,
function(status, response, clear) {
if (200 <= status && status < 300) {
if (response) {
if (action.isArray) {
value.length = 0;
forEach(response, function(item){
value.push(new Resource(item));
});
} else {
copy(response, value);
}
function(status, response, responseHeaders) {
if (response) {
if (action.isArray) {
value.length = 0;
forEach(response, function(item) {
value.push(new Resource(item));
});
} else {
copy(response, value);
}
(callback||noop)(value);
} else {
throw {status: status, response:response, message: status + ": " + response};
}
(success||noop)(value, responseHeaders);
},
error || action.verifyCache,
action.verifyCache);
return value;
};
@@ -120,18 +133,29 @@ ResourceFactory.prototype = {
return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions);
};
Resource.prototype['$' + name] = function(a1, a2){
var params = extractParams(this);
var callback = noop;
Resource.prototype['$' + name] = function(a1, a2, a3) {
var params = extractParams(this),
success = noop,
error;
switch(arguments.length) {
case 2: params = a1; callback = a2;
case 1: if (typeof a1 == $function) callback = a1; else params = a1;
case 3: params = a1; success = a2; error = a3; break;
case 2:
case 1:
if (isFunction(a1)) {
success = a1;
error = a2;
} else {
params = a1;
success = a2 || noop;
}
case 0: break;
default:
throw "Expected between 1-2 arguments [params, callback], got " + arguments.length + " arguments.";
throw "Expected between 1-3 arguments [params, success, error], got " +
arguments.length + " arguments.";
}
var data = isPostOrPut ? this : undefined;
Resource[name].call(this, params, data, callback);
Resource[name].call(this, params, data, success, error);
};
});
return Resource;
+5 -3
View File
@@ -1,3 +1,5 @@
'use strict';
function getter(instance, path, unboundFn) {
if (!path) return instance;
var element = path.split('.');
@@ -7,7 +9,7 @@ function getter(instance, path, unboundFn) {
for ( var i = 0; i < len; i++) {
key = element[i];
if (!key.match(/^[\$\w][\$\w\d]*$/))
throw "Expression '" + path + "' is not a valid expression for accesing variables.";
throw "Expression '" + path + "' is not a valid expression for accessing variables.";
if (instance) {
lastInstance = instance;
instance = instance[key];
@@ -200,7 +202,7 @@ function createScope(parent, providers, instanceCache) {
* @description
* Assigns a value to a property of the current scope specified via `property_chain`. Unlike in
* JavaScript, if there are any `undefined` intermediary properties, empty objects are created
* and assigned in to them instead of throwing an exception.
* and assigned to them instead of throwing an exception.
*
<pre>
var scope = angular.scope();
@@ -366,7 +368,7 @@ function createScope(parent, providers, instanceCache) {
* parameters, `newValue` and `oldValue`.
* @param {(function()|DOMElement)=} [exceptionHanlder=angular.service.$exceptionHandler] Handler
* that gets called when `watchExp` or `listener` throws an exception. If a DOMElement is
* specified as handler, the element gets decorated by angular with the information about the
* specified as a handler, the element gets decorated by angular with the information about the
* exception.
* @param {boolean=} [initRun=true] Flag that prevents the first execution of the listener upon
* registration.
+11 -25
View File
@@ -1,25 +1,9 @@
'use strict';
/**
* The MIT License
*
* Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* @license AngularJS
* (c) 2010-2011 AngularJS http://angularjs.org
* License: MIT
*/
(function(window) {
@@ -92,7 +76,7 @@
// load the js scripts
for (i in Array.prototype.slice.call(arguments, 0)) {
file = arguments[i];
var file = arguments[i];
document.write('<script type="text/javascript" src="' + serverPath + file + '" ' +
'onload="angularClobberTest(\'' + file + '\')"></script>');
}
@@ -150,10 +134,12 @@
// empty the cache to prevent mem leaks
globalVars = {};
//angular-ie-compat.js needs to be pregenerated for development with IE<8
if (msie<8) addScript('../angular-ie-compat.js');
var config = angularJsConfig(document);
angularInit(angularJsConfig(document), document);
// angular-ie-compat.js needs to be pregenerated for development with IE<8
config.ie_compat = serverPath + '../build/angular-ie-compat.js';
angularInit(config, document);
}
if (window.addEventListener){
+196 -38
View File
@@ -1,25 +1,9 @@
'use strict';
/**
* The MIT License
*
* Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2011 AngularJS http://angularjs.org
* License: MIT
*/
@@ -69,8 +53,8 @@
* Built-in mocks:
*
* * {@link angular.mock.service.$browser $browser } - A mock implementation of the browser.
* * {@link angular.mock.service.$exceptionHandler $exceptionHandler } - A mock implementation of the
* angular service exception handler.
* * {@link angular.mock.service.$exceptionHandler $exceptionHandler } - A mock implementation of
* the angular service exception handler.
* * {@link angular.mock.service.$log $log } - A mock implementation of the angular service log.
*/
angular.mock = {};
@@ -80,6 +64,24 @@ angular.mock = {};
* @workInProgress
* @ngdoc service
* @name angular.mock.service.$browser
*
* @description
* This service is a mock implementation of {@link angular.service.$browser}. It provides fake
* implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr,
* cookies.
*
* This implementation is automatically available and replaces regular `$browser` service in tests
* when `angular-mocks.js` is loaded.
*
* The api of this service is the same as the real {@link angular.service.$browser $browser}, except
* that there are several helper methods available which can be used in tests.
*
* The following apis can be used in tests:
*
* - {@link angular.mock.service.$browser.xhr $browser.xhr} — enables testing of code that uses
* the {@link angular.service.$xhr $xhr service} to make XmlHttpRequests.
* - $browser.defer — enables testing of code that uses
* {@link angular.service.$defer $defer service} for executing functions via the `setTimeout` api.
*/
function MockBrowser() {
var self = this,
@@ -108,6 +110,33 @@ function MockBrowser() {
};
/**
* @ngdoc function
* @name angular.mock.service.$browser.xhr
*
* @description
* Generic method for training browser to expect a request in a test and respond to it.
*
* See also convenience methods for browser training:
*
* - {@link angular.mock.service.$browser.xhr.expectGET $browser.xhr.expectGET}
* - {@link angular.mock.service.$browser.xhr.expectPOST $browser.xhr.expectPOST}
* - {@link angular.mock.service.$browser.xhr.expectPUT $browser.xhr.expectPUT}
* - {@link angular.mock.service.$browser.xhr.expectDELETE $browser.xhr.expectDELETE}
* - {@link angular.mock.service.$browser.xhr.expectJSON $browser.xhr.expectJSON}
*
* To flush pending requests in tests use
* {@link angular.mock.service.$browser.xhr.flush $browser.xhr.flush}.
*
* @param {string} method Expected HTTP method.
* @param {string} url Url path for which a request is expected.
* @param {(object|string)=} data Expected body of the (POST) HTTP request.
* @param {function(number, *)} callback Callback to call when response is flushed.
* @param {object} headers Key-value pairs of expected headers.
* @returns {object} Response configuration object. You can call its `respond()` method to
* configure what should the browser mock return when the response is
* {@link angular.mock.service.$browser.xhr.flush flushed}.
*/
self.xhr = function(method, url, data, callback, headers) {
headers = headers || {};
if (data && angular.isObject(data)) data = angular.toJson(data);
@@ -123,7 +152,14 @@ function MockBrowser() {
throw new Error("Missing HTTP request header: " + key + ": " + value);
}
});
callback(expectation.code, expectation.response);
callback(expectation.code, expectation.response, function(header) {
if (header) {
header = header.toLowerCase();
return expectation.responseHeaders && expectation.responseHeaders[header] || null;
} else {
return expectation.responseHeaders || {};
}
});
});
};
self.xhr.expectations = expectations;
@@ -133,20 +169,104 @@ function MockBrowser() {
if (data && angular.isString(data)) url += "|" + data;
var expect = expectations[method] || (expectations[method] = {});
return {
respond: function(code, response) {
respond: function(code, response, responseHeaders) {
if (!angular.isNumber(code)) {
responseHeaders = response;
response = code;
code = 200;
}
expect[url] = {code:code, response:response, headers: headers || {}};
angular.forEach(responseHeaders, function(value, key) {
delete responseHeaders[key];
responseHeaders[key.toLowerCase()] = value;
});
expect[url] = {
code: code,
response: response,
headers: headers || {},
responseHeaders: responseHeaders || {}
};
}
};
};
/**
* @ngdoc function
* @name angular.mock.service.$browser.xhr.expectGET
*
* @description
* Trains browser to expect a `GET` request and respond to it.
*
* @param {string} url Url path for which a request is expected.
* @returns {object} Response configuration object. You can call its `respond()` method to
* configure what should the browser mock return when the response is
* {@link angular.mock.service.$browser.xhr.flush flushed}.
*/
self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET');
/**
* @ngdoc function
* @name angular.mock.service.$browser.xhr.expectPOST
*
* @description
* Trains browser to expect a `POST` request and respond to it.
*
* @param {string} url Url path for which a request is expected.
* @returns {object} Response configuration object. You can call its `respond()` method to
* configure what should the browser mock return when the response is
* {@link angular.mock.service.$browser.xhr.flush flushed}.
*/
self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST');
/**
* @ngdoc function
* @name angular.mock.service.$browser.xhr.expectDELETE
*
* @description
* Trains browser to expect a `DELETE` request and respond to it.
*
* @param {string} url Url path for which a request is expected.
* @returns {object} Response configuration object. You can call its `respond()` method to
* configure what should the browser mock return when the response is
* {@link angular.mock.service.$browser.xhr.flush flushed}.
*/
self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE');
/**
* @ngdoc function
* @name angular.mock.service.$browser.xhr.expectPUT
*
* @description
* Trains browser to expect a `PUT` request and respond to it.
*
* @param {string} url Url path for which a request is expected.
* @returns {object} Response configuration object. You can call its `respond()` method to
* configure what should the browser mock return when the response is
* {@link angular.mock.service.$browser.xhr.flush flushed}.
*/
self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT');
/**
* @ngdoc function
* @name angular.mock.service.$browser.xhr.expectJSON
*
* @description
* Trains browser to expect a `JSON` request and respond to it.
*
* @param {string} url Url path for which a request is expected.
* @returns {object} Response configuration object. You can call its `respond()` method to
* configure what should the browser mock return when the response is
* {@link angular.mock.service.$browser.xhr.flush flushed}.
*/
self.xhr.expectJSON = angular.bind(self, self.xhr.expect, 'JSON');
/**
* @ngdoc function
* @name angular.mock.service.$browser.xhr.flush
*
* @description
* Flushes all pending requests and executes xhr callbacks with the trained response as the
* argument.
*/
self.xhr.flush = function() {
if (requests.length == 0) {
throw new Error("No xhr requests to be flushed!");
@@ -160,17 +280,41 @@ function MockBrowser() {
self.cookieHash = {};
self.lastCookieHash = {};
self.deferredFns = [];
self.deferredNextId = 0;
self.defer = function(fn, delay) {
delay = delay || 0;
self.deferredFns.push({time:(self.defer.now + delay), fn:fn});
self.deferredFns.sort(function(a,b){ return a.time - b.time;});
self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
self.deferredFns.sort(function(a,b){return a.time - b.time;});
return self.deferredNextId++;
};
self.defer.now = 0;
self.defer.flush = function(time) {
self.defer.now += (time || 0);
self.defer.cancel = function(deferId) {
var fnIndex;
angular.forEach(self.deferredFns, function(fn, index) {
if (fn.id === deferId) fnIndex = index;
});
if (fnIndex !== undefined) {
self.deferredFns.splice(fnIndex, 1);
}
};
self.defer.flush = function(delay) {
if (angular.isDefined(delay)) {
self.defer.now += delay;
} else {
if (self.deferredFns.length) {
self.defer.now = self.deferredFns[self.deferredFns.length-1].time;
}
}
while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) {
self.deferredFns.shift().fn();
}
@@ -178,6 +322,13 @@ function MockBrowser() {
}
MockBrowser.prototype = {
/**
* @name angular.mock.service.$browser#poll
* @methodOf angular.mock.service.$browser
*
* @description
* run all fns in pollFns
*/
poll: function poll(){
angular.forEach(this.pollFns, function(pollFn){
pollFn();
@@ -239,7 +390,7 @@ angular.service('$browser', function(){
*
* See {@link angular.mock} for more info on angular mocks.
*/
angular.service('$exceptionHandler', function(e) {
angular.service('$exceptionHandler', function() {
return function(e) {throw e;};
});
@@ -260,10 +411,10 @@ angular.service('$log', MockLogFactory);
function MockLogFactory() {
var $log = {
log: function(){ $log.log.logs.push(arguments); },
warn: function(){ $log.warn.logs.push(arguments); },
info: function(){ $log.info.logs.push(arguments); },
error: function(){ $log.error.logs.push(arguments); }
log: function(){$log.log.logs.push(arguments);},
warn: function(){$log.warn.logs.push(arguments);},
info: function(){$log.info.logs.push(arguments);},
error: function(){$log.error.logs.push(arguments);}
};
$log.log.logs = [];
@@ -305,7 +456,7 @@ function MockLogFactory() {
* </pre>
*
*/
function TzDate(offset, timestamp) {
function TzDate(offset, timestamp, toStringVal) {
if (angular.isString(timestamp)) {
var tsStr = timestamp;
@@ -329,6 +480,10 @@ function TzDate(offset, timestamp) {
return this.date.getTime() - this.offsetDiff;
};
this.toString = function() {
return toStringVal;
};
this.toLocaleDateString = function() {
return this.date.toLocaleDateString();
};
@@ -390,7 +545,8 @@ function TzDate(offset, timestamp) {
};
//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getMilliseconds', 'getTime', 'getUTCDay',
var self = this,
unimplementedMethods = ['getMilliseconds', 'getUTCDay',
'getUTCMilliseconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
@@ -398,7 +554,9 @@ function TzDate(offset, timestamp) {
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
angular.forEach(unimplementedMethods, function(methodName) {
this[methodName] = function() {
if (methodName == 'toString' && toStringVal) return;
self[methodName] = function() {
throw {
name: "MethodNotImplemented",
message: "Method '" + methodName + "' is not implemented in the TzDate mock"
+4 -21
View File
@@ -1,24 +1,7 @@
/**
* The MIT License
*
* Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2011 AngularJS http://angularjs.org
* License: MIT
*/
'USE STRICT';
(function(window, document, undefined){
+2
View File
@@ -1,3 +1,5 @@
'use strict';
var angularGlobal = {
'typeOf':function(obj){
if (obj === null) return $null;
+15 -13
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc overview
@@ -263,7 +265,7 @@ angularDirective("ng:bind", function(expression, element){
error = formatError(e);
});
this.$element = oldElement;
// If we are HTML than save the raw HTML data so that we don't
// If we are HTML then save the raw HTML data so that we don't
// recompute sanitization since it is expensive.
// TODO: turn this into a more generic way to compute this
if (isHtml = (value instanceof HTML))
@@ -339,7 +341,7 @@ function compileBindTemplate(template){
* text should be replaced with the template in ng:bind-template.
* Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}`
* expressions. (This is required since some HTML elements
* can not have SPAN elements such as TITLE, or OPTION to name a few.
* can not have SPAN elements such as TITLE, or OPTION to name a few.)
*
* @element ANY
* @param {string} template of form
@@ -539,19 +541,14 @@ angularDirective("ng:click", function(expression, element){
<form ng:submit="list.push(text);text='';" ng:init="list=[]">
Enter text and hit enter:
<input type="text" name="text" value="hello"/>
<input type="submit" id="submit" value="Submit" />
</form>
<pre>list={{list}}</pre>
</doc:source>
<doc:scenario>
it('should check ng:submit', function(){
expect(binding('list')).toBe('list=[]');
element('.doc-example-live form input').click();
this.addFutureAction('submit from', function($window, $document, done) {
$window.angular.element(
$document.elements('.doc-example-live form')).
trigger('submit');
done();
});
element('.doc-example-live #submit').click();
expect(binding('list')).toBe('list=["hello"]');
});
</doc:scenario>
@@ -574,9 +571,14 @@ function ngClass(selector) {
var existing = element[0].className + ' ';
return function(element){
this.$onEval(function(){
if (selector(this.$index)) {
var value = this.$eval(expression);
var scope = this;
if (selector(scope.$index)) {
var ngClassVal = scope.$eval(element.attr('ng:class') || '');
if (isArray(ngClassVal)) ngClassVal = ngClassVal.join(' ');
var value = scope.$eval(expression);
if (isArray(value)) value = value.join(' ');
if (ngClassVal && ngClassVal !== value) value = value + ' ' + ngClassVal;
element[0].className = trim(existing + value);
}
}, element);
@@ -736,7 +738,7 @@ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
angularDirective("ng:show", function(expression, element){
return function(element){
this.$onEval(function(){
element.css($display, toBoolean(this.$eval(expression)) ? '' : $none);
element.css('display', toBoolean(this.$eval(expression)) ? '' : 'none');
}, element);
};
});
@@ -777,7 +779,7 @@ angularDirective("ng:show", function(expression, element){
angularDirective("ng:hide", function(expression, element){
return function(element){
this.$onEval(function(){
element.css($display, toBoolean(this.$eval(expression)) ? $none : '');
element.css('display', toBoolean(this.$eval(expression)) ? 'none' : '');
}, element);
};
});
+164 -62
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc overview
@@ -33,36 +35,42 @@
* @function
*
* @description
* Formats a number as a currency (ie $1,234.56).
* Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
* symbol for current locale is used.
*
* @param {number} amount Input to filter.
* @returns {string} Formated number.
* @param {string=} symbol Currency symbol or identifier to be displayed.
* @returns {string} Formatted number.
*
* @css ng-format-negative
* When the value is negative, this css class is applied to the binding making it by default red.
* When the value is negative, this css class is applied to the binding making it (by default) red.
*
* @example
<doc:example>
<doc:source>
<input type="text" name="amount" value="1234.56"/> <br/>
{{amount | currency}}
default currency symbol ($): {{amount | currency}}<br/>
custom currency identifier (USD$): {{amount | currency:"USD$"}}
</doc:source>
<doc:scenario>
it('should init with 1234.56', function(){
expect(binding('amount | currency')).toBe('$1,234.56');
expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
});
it('should update', function(){
input('amount').enter('-1234');
expect(binding('amount | currency')).toBe('$-1,234.00');
expect(binding('amount | currency')).toBe('($1,234.00)');
expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
expect(element('.doc-example-live .ng-binding').attr('className')).
toMatch(/ng-format-negative/);
});
</doc:scenario>
</doc:example>
*/
angularFilter.currency = function(amount){
angularFilter.currency = function(amount, currencySymbol){
this.$element.toggleClass('ng-format-negative', amount < 0);
return '$' + angularFilter.number.apply(this, [amount, 2]);
if (isUndefined(currencySymbol)) currencySymbol = NUMBER_FORMATS.CURRENCY_SYM;
return formatNumber(amount, 2, 1).replace(/\u00A4/g, currencySymbol);
};
/**
@@ -72,9 +80,9 @@ angularFilter.currency = function(amount){
* @function
*
* @description
* Formats a number as text.
* Formats a number as text.
*
* If the input is not a number empty string is returned.
* If the input is not a number an empty string is returned.
*
* @param {number|string} number Number to format.
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
@@ -90,59 +98,104 @@ angularFilter.currency = function(amount){
</doc:source>
<doc:scenario>
it('should format numbers', function(){
expect(binding('val | number')).toBe('1,234.57');
expect(binding('val | number')).toBe('1,234.568');
expect(binding('val | number:0')).toBe('1,235');
expect(binding('-val | number:4')).toBe('-1,234.5679');
});
it('should update', function(){
input('val').enter('3374.333');
expect(binding('val | number')).toBe('3,374.33');
expect(binding('val | number')).toBe('3,374.333');
expect(binding('val | number:0')).toBe('3,374');
expect(binding('-val | number:4')).toBe('-3,374.3330');
});
</doc:scenario>
</doc:example>
*/
angularFilter.number = function(number, fractionSize){
if (isNaN(number) || !isFinite(number)) {
return '';
}
fractionSize = isUndefined(fractionSize)? 2 : fractionSize;
// PATTERNS[0] is an array for Decimal Pattern, PATTERNS[1] is an array Currency Pattern
// Following is the order in each pattern array:
// 0: minInteger,
// 1: minFraction,
// 2: maxFraction,
// 3: positivePrefix,
// 4: positiveSuffix,
// 5: negativePrefix,
// 6: negativeSuffix,
// 7: groupSize,
// 8: lastGroupSize
var NUMBER_FORMATS = {
DECIMAL_SEP: '.',
GROUP_SEP: ',',
PATTERNS: [[1, 0, 3, '', '', '-', '', 3, 3],[1, 2, 2, '\u00A4', '', '(\u00A4', ')', 3, 3]],
CURRENCY_SYM: '$'
};
var DECIMAL_SEP = '.';
angularFilter.number = function(number, fractionSize) {
if (isNaN(number) || !isFinite(number)) return '';
return formatNumber(number, fractionSize, 0);
};
function formatNumber(number, fractionSize, type) {
var isNegative = number < 0,
pow = Math.pow(10, fractionSize),
whole = '' + number,
type = type || 0, // 0 is decimal pattern, 1 is currency pattern
pattern = NUMBER_FORMATS.PATTERNS[type];
number = Math.abs(number);
var numStr = number + '',
formatedText = '',
i;
parts = [];
if (whole.indexOf('e') > -1) return whole;
if (numStr.indexOf('e') !== -1) {
var formatedText = numStr;
} else {
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
number = Math.round(number * pow) / pow;
fraction = ('' + number).split('.');
whole = fraction[0];
fraction = fraction[1] || '';
if (isNegative) {
formatedText = '-';
whole = whole.substring(1);
}
for (i = 0; i < whole.length; i++) {
if ((whole.length - i)%3 === 0 && i !== 0) {
formatedText += ',';
//determine fractionSize if it is not specified
if (isUndefined(fractionSize)) {
fractionSize = Math.min(Math.max(pattern[1], fractionLen), pattern[2]);
}
formatedText += whole.charAt(i);
}
if (fractionSize) {
var pow = Math.pow(10, fractionSize);
number = Math.round(number * pow) / pow;
var fraction = ('' + number).split(DECIMAL_SEP);
var whole = fraction[0];
fraction = fraction[1] || '';
var pos = 0,
lgroup = pattern[8],
group = pattern[7];
if (whole.length >= (lgroup + group)) {
pos = whole.length - lgroup;
for (var i = 0; i < pos; i++) {
if ((pos - i)%group === 0 && i !== 0) {
formatedText += NUMBER_FORMATS.GROUP_SEP;
}
formatedText += whole.charAt(i);
}
}
for (i = pos; i < whole.length; i++) {
if ((whole.length - i)%lgroup === 0 && i !== 0) {
formatedText += NUMBER_FORMATS.GROUP_SEP;
}
formatedText += whole.charAt(i);
}
// format fraction part.
while(fraction.length < fractionSize) {
fraction += '0';
}
formatedText += '.' + fraction.substring(0, fractionSize);
if (fractionSize) formatedText += NUMBER_FORMATS.DECIMAL_SEP + fraction.substr(0, fractionSize);
}
return formatedText;
};
parts.push(isNegative ? pattern[5] : pattern[3]);
parts.push(formatedText);
parts.push(isNegative ? pattern[6] : pattern[4]);
return parts.join('');
}
function padNumber(num, digits, trim) {
var neg = '';
@@ -182,6 +235,17 @@ function dateStrGetter(name, shortForm) {
};
}
function timeZoneGetter(numFormat) {
return function(date) {
var timeZone;
if (numFormat || !(timeZone = GET_TIME_ZONE.exec(date.toString()))) {
var offset = date.getTimezoneOffset();
return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
}
return timeZone[0];
};
}
var DAY = 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(',');
var MONTH = 'January,February,March,April,May,June,July,August,September,October,November,December'.
@@ -190,7 +254,8 @@ var MONTH = 'January,February,March,April,May,June,July,August,September,October
var DATE_FORMATS = {
yyyy: dateGetter('FullYear', 4),
yy: dateGetter('FullYear', 2, 0, true),
MMMMM: dateStrGetter('Month'),
y: dateGetter('FullYear', 1),
MMMM: dateStrGetter('Month'),
MMM: dateStrGetter('Month', true),
MM: dateGetter('Month', 2, 1),
M: dateGetter('Month', 1, 1),
@@ -207,14 +272,26 @@ var DATE_FORMATS = {
EEEE: dateStrGetter('Day'),
EEE: dateStrGetter('Day', true),
a: function(date){return date.getHours() < 12 ? 'am' : 'pm';},
Z: function(date){
var offset = date.getTimezoneOffset();
return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
}
z: timeZoneGetter(false),
Z: timeZoneGetter(true)
};
var DEFAULT_DATETIME_FORMATS = {
long: 'MMMM d, y h:mm:ss a z',
medium: 'MMM d, y h:mm:ss a',
short: 'M/d/yy h:mm a',
fullDate: 'EEEE, MMMM d, y',
longDate: 'MMMM d, y',
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
longTime: 'h:mm:ss a z',
mediumTime: 'h:mm:ss a',
shortTime: 'h:mm a'
};
var DATE_FORMATS_SPLIT = /([^yMdHhmsaZE]*)(E+|y+|M+|d+|H+|h+|m+|s+|a|Z)(.*)/;
var GET_TIME_ZONE = /[A-Z]{3}(?![+\-])/;
var DATE_FORMATS_SPLIT = /([^yMdHhmsazZE]*)(E+|y+|M+|d+|H+|h+|m+|s+|a|Z|z)(.*)/;
var OPERA_TOSTRING_PATTERN = /^[\d].*Z$/;
var NUMBER_STRING = /^\d+$/;
@@ -229,35 +306,57 @@ var NUMBER_STRING = /^\d+$/;
*
* `format` string can be composed of the following elements:
*
* * `'yyyy'`: 4 digit representation of year e.g. 2010
* * `'yy'`: 2 digit representation of year, padded (00-99)
* * `'MMMMM'`: Month in year (JanuaryDecember)
* * `'MMM'`: Month in year (Jan - Dec)
* * `'MM'`: Month in year, padded (0112)
* * `'M'`: Month in year (112)
* * `'dd'`: Day in month, padded (0131)
* * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
* * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
* * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
* * `'MMMM'`: Month in year (January-December)
* * `'MMM'`: Month in year (Jan-Dec)
* * `'MM'`: Month in year, padded (01-12)
* * `'M'`: Month in year (1-12)
* * `'dd'`: Day in month, padded (01-31)
* * `'d'`: Day in month (1-31)
* * `'EEEE'`: Day in Week,(SundaySaturday)
* * `'EEEE'`: Day in Week,(Sunday-Saturday)
* * `'EEE'`: Day in Week, (Sun-Sat)
* * `'HH'`: Hour in day, padded (0023)
* * `'HH'`: Hour in day, padded (00-23)
* * `'H'`: Hour in day (0-23)
* * `'hh'`: Hour in am/pm, padded (0112)
* * `'hh'`: Hour in am/pm, padded (01-12)
* * `'h'`: Hour in am/pm, (1-12)
* * `'mm'`: Minute in hour, padded (0059)
* * `'mm'`: Minute in hour, padded (00-59)
* * `'m'`: Minute in hour (0-59)
* * `'ss'`: Second in minute, padded (0059)
* * `'s'`: Second in minute (059)
* * `'ss'`: Second in minute, padded (00-59)
* * `'s'`: Second in minute (0-59)
* * `'a'`: am/pm marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-12001200)
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
* * `'z'`: short form of current timezone name (e.g. PDT)
*
* `format` string can also be the following default formats for `en_US` locale (support for other
* locales will be added in the future versions):
*
* * `'long'`: equivalent to `'MMMM d, y h:mm:ss a z'` for en_US locale
* (e.g. September 3, 2010 12:05:08 pm PDT)
* * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
* (e.g. Sep 3, 2010 12:05:08 pm)
* * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
* * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
* (e.g. Friday, September 3, 2010)
* * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010
* * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
* * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
* * `'longTime'`: equivalent to `'h:mm:ss a z'` for en_US locale (e.g. 12:05:08 pm PDT)
* * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
* * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
*
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
* number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ).
* @param {string=} format Formatting rules. If not specified, Date#toLocaleDateString is used.
* @param {string=} format Formatting rules (see Description). If not specified,
* Date#toLocaleDateString is used.
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
*
* @example
<doc:example>
<doc:source>
<span ng:non-bindable>{{1288323623006 | date:'medium'}}</span>:
{{1288323623006 | date:'medium'}}<br/>
<span ng:non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/>
<span ng:non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
@@ -265,6 +364,8 @@ var NUMBER_STRING = /^\d+$/;
</doc:source>
<doc:scenario>
it('should format date', function(){
expect(binding("1288323623006 | date:'medium'")).
toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (am|pm)/);
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
@@ -274,6 +375,7 @@ var NUMBER_STRING = /^\d+$/;
</doc:example>
*/
angularFilter.date = function(date, format) {
format = DEFAULT_DATETIME_FORMATS[format] || format;
if (isString(date)) {
if (NUMBER_STRING.test(date)) {
date = parseInt(date, 10);
@@ -390,7 +492,7 @@ angularFilter.uppercase = uppercase;
*
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however since our parser is more strict than a typical browser
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
*
@@ -479,7 +581,7 @@ angularFilter.html = function(html, option){
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plane email address links.
* plain email address links.
*
* @param {string} text Input text.
* @returns {string} Html-linkified text.
+5 -4
View File
@@ -1,20 +1,21 @@
'use strict';
/**
* @workInProgress
* @ngdoc overview
* @name angular.formatter
* @description
*
* Formatters are used for translating data formats between those used in for display and those used
* Formatters are used for translating data formats between those used for display and those used
* for storage.
*
* Following is the list of built-in angular formatters:
*
* * {@link angular.formatter.boolean boolean} - Formats user input in boolean format
* * {@link angular.formatter.index index} - Manages indexing into an HTML select widget
* * {@link angular.formatter.json json} - Formats user input in JSON format
* * {@link angular.formatter.list list} - Formats user input string as an array
* * {@link angular.formatter.number} - Formats user input strings as a number
* * {@link angular.formatter.trim} - Trims extras spaces from end of user input
* * {@link angular.formatter.number number} - Formats user input strings as a number
* * {@link angular.formatter.trim trim} - Trims extras spaces from end of user input
*
* For more information about how angular formatters work, and how to create your own formatters,
* see {@link guide/dev_guide.templates.formatters Understanding Angular Formatters} in the angular
+19 -24
View File
@@ -1,3 +1,5 @@
'use strict';
//////////////////////////////////
//JQLite
//////////////////////////////////
@@ -21,7 +23,7 @@
* focus on the most commonly needed functionality and minimal footprint. For this reason only a
* limited number of jQuery methods, arguments and invocation styles are supported.
*
* NOTE: All element references in angular are always wrapped with jQuery (lite) and are never
* 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:
@@ -44,6 +46,7 @@
* - [replaceWith()](http://api.jquery.com/replaceWith/)
* - [text()](http://api.jquery.com/text/)
* - [trigger()](http://api.jquery.com/trigger/)
* - [eq()](http://api.jquery.com/eq/)
*
* ## Additionally these methods extend the jQuery and are available in both jQuery and jQuery lite
* version:
@@ -84,23 +87,6 @@ function getStyle(element) {
return current;
}
//TODO: delete me! dead code?
if (msie) {
extend(JQLite.prototype, {
text: function(value) {
var e = this[0];
// NodeType == 3 is text node
if (e.nodeType == 3) {
if (isDefined(value)) e.nodeValue = value;
return e.nodeValue;
} else {
if (isDefined(value)) e.innerText = value;
return e.innerText;
}
}
});
}
/////////////////////////////////////////////
function jqLiteWrap(element) {
if (isString(element) && element.charAt(0) != '<') {
@@ -164,7 +150,7 @@ function JQLiteData(element, key, value) {
function JQLiteHasClass(element, selector, _) {
// the argument '_' is important, since it makes the function have 3 arguments, which
// is neede for delegate function to realize the this is a getter.
// is needed for delegate function to realize the this is a getter.
var className = " " + selector + " ";
return ((" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1);
}
@@ -211,11 +197,16 @@ var JQLitePrototype = JQLite.prototype = {
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
jqLiteWrap(window).bind('load', trigger); // fallback to window.onload for others
},
toString: function(){
toString: function() {
var value = [];
forEach(this, function(e){ value.push('' + e);});
return '[' + value.join(', ') + ']';
},
eq: function(index) {
return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
},
length: 0,
push: push,
sort: [].sort,
@@ -266,7 +257,9 @@ forEach({
} else if (element.getAttribute) {
// the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
// some elements (e.g. Document) don't have get attribute, so return undefined
return element.getAttribute(name, 2);
var ret = element.getAttribute(name, 2);
// normalize non-existing attributes to undefined (as jQuery)
return ret === null ? undefined : ret;
}
},
@@ -351,11 +344,10 @@ forEach({
dealoc: JQLiteDealoc,
bind: function(element, type, fn){
var bind = JQLiteData(element, 'bind'),
eventHandler;
var bind = JQLiteData(element, 'bind');
if (!bind) JQLiteData(element, 'bind', bind = {});
forEach(type.split(' '), function(type){
eventHandler = bind[type];
var eventHandler = bind[type];
if (!eventHandler) {
bind[type] = eventHandler = function(event) {
if (!event.preventDefault) {
@@ -368,6 +360,9 @@ forEach({
event.cancelBubble = true; //ie
};
}
if (!event.target) {
event.target = event.srcElement || document;
}
forEach(eventHandler.fns, function(fn){
fn.call(element, event);
});
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* JSTestDriver adapter for angular scenario tests
*
+3 -21
View File
@@ -1,24 +1,6 @@
/**
* The MIT License
*
* Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2011 AngularJS http://angularjs.org
* License: MIT
*/
(function(window) {
+17 -10
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc overview
@@ -10,8 +12,8 @@
* 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}.
*
* The most prominent example of an markup in angular is the built-in double curly markup
* `{{expression}}`, which is a shorthand for `<span ng:bind="expression"></span>`.
* The most prominent example of a markup in angular is the built-in double curly markup
* `{{expression}}`, which is shorthand for `<span ng:bind="expression"></span>`.
*
* Create custom markup like this:
*
@@ -32,7 +34,7 @@
* @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 content of a
* except that it allows you to modify the state of the attribute text rather than the content of a
* node.
*
* Create custom attribute markup like this:
@@ -136,7 +138,7 @@ angularTextMarkup('option', function(text, textNode, parentElement){
*
* @description
* Using <angular/> markup like {{hash}} in an href attribute makes
* the page open to a wrong URL, ff the user clicks that link before
* the page open to a wrong URL, if the user clicks that link before
* angular has a chance to replace the {{hash}} with actual URL, the
* link will be broken and will most likely return a 404 error.
* The `ng:href` solves this problem by placing the `href` in the
@@ -249,7 +251,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* </div>
* </pre>
*
* the HTML specs do not require browsers preserve the special attributes such as disabled.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as disabled.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:disabled.
*
@@ -279,7 +282,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:checked
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as checked.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as checked.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:checked.
* @example
@@ -308,7 +312,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:multiple
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as multiple.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as multiple.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:multiple.
*
@@ -319,7 +324,7 @@ angularTextMarkup('option', function(text, textNode, parentElement){
<select id="select" ng:multiple="{{checked}}">
<option>Misko</option>
<option>Igor</option>
<option>Vojita</option>
<option>Vojta</option>
<option>Di</option>
</select>
</doc:source>
@@ -343,7 +348,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:readonly
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as readonly.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as readonly.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:readonly.
* @example
@@ -372,7 +378,8 @@ angularTextMarkup('option', function(text, textNode, parentElement){
* @name angular.directive.ng:selected
*
* @description
* the HTML specs do not require browsers preserve the special attributes such as selected.(The presense of them means true and absense means false)
* The HTML specs do not require browsers to preserve the special attributes such as selected.
* (The presence of them means true and absence means false)
* This prevents the angular compiler from correctly retrieving the binding expression.
* To solve this problem, we introduce ng:selected.
* @example
+2 -2
View File
@@ -1,3 +1,5 @@
'use strict';
var OPERATORS = {
'null':function(self){return null;},
'true':function(self){return true;},
@@ -290,11 +292,9 @@ function parser(text, json){
var token = peek(e1, e2, e3, e4);
if (token) {
if (json && !token.json) {
index = token.index;
throwError("is not valid json", token);
}
tokens.shift();
this.currentToken = token;
return token;
}
return false;
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/*
* HTML Parser By Misko Hevery (misko@hevery.com)
* based on: HTML Parser By John Resig (ejohn.org)
+2 -1
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Represents the application currently being tested and abstracts usage
* of iframes or separate windows.
@@ -89,7 +91,6 @@ angular.scenario.Application.prototype.executeAction = function(action) {
return action.call(this, $window, _jQuery($window.document));
}
var $browser = $window.angular.service.$browser();
$browser.poll();
$browser.notifyWhenNoOutstandingRequests(function() {
action.call(self, $window, _jQuery($window.document));
});
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* The representation of define blocks. Don't used directly, instead use
* define() in your tests.
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* A future action in a spec.
*
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Maintains an object tree from the runner events.
*
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Runner for scenarios
*
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Setup file for the Scenario.
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* This class is the "this" of the it/beforeEach/afterEach method.
* Responsibilities:
+2
View File
@@ -1,3 +1,5 @@
'use strict';
(function(previousOnLoad){
var prefix = (function(){
var filename = /(.*\/)angular-bootstrap.js(#(.*))?/;
+3 -21
View File
@@ -1,25 +1,7 @@
/**
* The MIT License
*
* Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2011 AngularJS http://angularjs.org
* License: MIT
*/
(function(window, document){
var _jQuery = window.jQuery.noConflict(true);
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Shared DSL statements that are useful to all scenarios.
*/
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Matchers for implementing specs. Follows the Jasmine spec conventions.
*/
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* User Interface for the Scenario Runner.
*
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Generates JSON output into a context.
*/
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Creates a global value $result with the result of the runner.
*/
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* Generates XML output into a context.
*/
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+2
View File
@@ -1,3 +1,5 @@
'use strict';
var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/,
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
+2
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
+16 -8
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
@@ -80,9 +82,9 @@
* The action methods on the class object or instance object can be invoked with the following
* parameters:
*
* - HTTP GET "class" actions: `Resource.action([parameters], [callback])`
* - non-GET "class" actions: `Resource.action(postData, [parameters], [callback])`
* - non-GET instance actions: `instance.$action([parameters], [callback])`
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
* - non-GET "class" actions: `Resource.action(postData, [parameters], [success], [error])`
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
*
*
* @example
@@ -140,14 +142,20 @@
});
</pre>
*
* It's worth noting that the callback for `get`, `query` and other method gets passed in the
* response that came from the server, so one could rewrite the above example as:
* It's worth noting that the success callback for `get`, `query` and other method gets passed
* in the response that came from the server, so one could rewrite the above example as:
*
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
User.get({userId:123}, function(u){
User.get({userId:123}, function(u, responseHeaders){
u.abc = true;
u.$save();
u.$save(function(u, responseHeaders) {
// Get an Object containing all response headers
var allHeaders = responseHeaders();
// Get a specific response header
u.newId = responseHeaders('Location');
});
});
</pre>
@@ -155,7 +163,7 @@
Let's look at what a buzz client created with the `$resource` service looks like:
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<script>
function BuzzController($resource) {
this.Activity = $resource(
+53 -22
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
@@ -20,15 +22,18 @@
Try changing the URL in the input box to see changes.
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<script>
angular.service('myApp', function($route) {
$route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl});
$route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl});
function MainCntl($route, $location) {
this.$route = $route;
this.$location = $location;
$route.when('/Book/:bookId', {template: 'examples/book.html', controller: BookCntl});
$route.when('/Book/:bookId/ch/:chapterId', {template: 'examples/chapter.html', controller: ChapterCntl});
$route.onChange(function() {
$route.current.scope.params = $route.current.params;
});
}, {$inject: ['$route']});
}
function BookCntl() {
this.name = "BookCntl";
@@ -39,18 +44,19 @@
}
</script>
Chose:
<a href="#/Book/Moby">Moby</a> |
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
<a href="#/Book/Gatsby">Gatsby</a> |
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
<input type="text" name="$location.hashPath" size="80" />
<pre>$location={{$location}}</pre>
<pre>$route.current.template={{$route.current.template}}</pre>
<pre>$route.current.params={{$route.current.params}}</pre>
<pre>$route.current.scope.name={{$route.current.scope.name}}</pre>
<hr/>
<ng:include src="$route.current.template" scope="$route.current.scope"/>
<div ng:controller="MainCntl">
Choose:
<a href="#/Book/Moby">Moby</a> |
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
<a href="#/Book/Gatsby">Gatsby</a> |
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
$location.hashPath: <input type="text" name="$location.hashPath" size="80" />
<pre>$route.current.template = {{$route.current.template}}</pre>
<pre>$route.current.params = {{$route.current.params}}</pre>
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
<hr />
<ng:view></ng:view>
</div>
</doc:source>
<doc:scenario>
</doc:scenario>
@@ -62,6 +68,8 @@ angularServiceInject('$route', function(location, $updateView) {
matcher = switchRouteMatcher,
parentScope = this,
dirty = 0,
lastHashPath,
lastRouteParams,
$route = {
routes: routes,
@@ -130,6 +138,18 @@ angularServiceInject('$route', function(location, $updateView) {
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.hash`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when $location.hashSearch
* changes. If this option is disabled, you should set up a $watch to be notified of
* param (hashSearch) changes as follows:
*
* function MyCtrl($route) {
* this.$watch(function() {
* return $route.current.params.myHashSearchParam;
* }, function(params) {
* //do stuff with params
* });
* }
*
* @returns {Object} route object
*
* @description
@@ -138,8 +158,8 @@ angularServiceInject('$route', function(location, $updateView) {
when:function (path, params) {
if (isUndefined(path)) return routes; //TODO(im): remove - not needed!
var route = routes[path];
if (!route) route = routes[path] = {};
if (params) extend(route, params);
if (!route) route = routes[path] = {reloadOnSearch: true};
if (params) extend(route, params); //TODO(im): what the heck? merge two route definitions?
dirty++;
return route;
},
@@ -177,14 +197,16 @@ angularServiceInject('$route', function(location, $updateView) {
function switchRouteMatcher(on, when, dstName) {
var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
// TODO(i): this code is convoluted and inefficient, we should construct the route matching
// regex only once and then reuse it
var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
params = [],
dst = {};
forEach(when.split(/\W/), function(param){
if (param) {
var paramRegExp = new RegExp(":" + param + "([\\W])");
if (regex.match(paramRegExp)) {
regex = regex.replace(paramRegExp, "([^\/]*)$1");
regex = regex.replace(paramRegExp, "([^\\/]*)$1");
params.push(param);
}
}
@@ -203,6 +225,14 @@ angularServiceInject('$route', function(location, $updateView) {
function updateRoute(){
var childScope, routeParams, pathParams, segmentMatch, key, redir;
if ($route.current) {
if (!$route.current.reloadOnSearch && (lastHashPath == location.hashPath)) {
$route.current.params = extend({}, location.hashSearch, lastRouteParams);
return;
}
}
lastHashPath = location.hashPath;
$route.current = null;
forEach(routes, function(rParams, rPath) {
if (!pathParams) {
@@ -249,6 +279,7 @@ angularServiceInject('$route', function(location, $updateView) {
scope: childScope,
params: extend({}, location.hashSearch, pathParams)
});
lastRouteParams = pathParams;
}
//fire onChange callbacks
@@ -260,7 +291,7 @@ angularServiceInject('$route', function(location, $updateView) {
}
this.$watch(function(){return dirty + location.hash;}, updateRoute);
this.$watch(function(){ return dirty + location.hash; }, updateRoute);
return $route;
}, ['$location', '$updateView']);
+3 -1
View File
@@ -1,3 +1,5 @@
'use strict';
/**
* @workInProgress
* @ngdoc service
@@ -32,7 +34,7 @@
* or 'XHR' (instead of {@link angular.service.$xhr}) then you may be changing the model
* without angular knowledge and you may need to call '$updateView()' directly.
*
* NOTE: if you wish to update the view immediately (without delay), you can do so by calling
* Note: if you wish to update the view immediately (without delay), you can do so by calling
* {@link angular.scope.$eval} at any time from your code:
* <pre>scope.$root.$eval()</pre>
*

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