Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76afa406b1 | |||
| f3c77858be | |||
| 96758c1c52 | |||
| 006fb4fbeb | |||
| 075c089b5c | |||
| 2b87c814ab | |||
| 2b1b257034 | |||
| 73caf76225 | |||
| dbb92efd13 | |||
| 1214084e9d | |||
| a18926f986 | |||
| b806b30861 | |||
| 43d15f830f | |||
| 1d26acb874 | |||
| 983c309542 | |||
| 904b69c745 | |||
| c65c34ebfe | |||
| 8ebe5ccd9a | |||
| e61fd1b43a | |||
| ce15a3e049 | |||
| 46bb08a9d0 | |||
| 94dd685709 | |||
| dc32ea627e | |||
| eafe15f54c | |||
| 666f326c5d | |||
| 908785960d | |||
| 5cc245dd80 | |||
| 0bd0ef7813 | |||
| 0c7252f929 | |||
| b94fb5c8c1 | |||
| c322735f83 | |||
| 9260b4937d | |||
| e9ccec76a6 | |||
| 2037facc99 | |||
| b2d0a386f6 | |||
| 6d7e7fdea6 | |||
| df72852f34 | |||
| c4f6ccb065 | |||
| 0c49bbdc38 | |||
| 7d074a3775 | |||
| dceafd32ee | |||
| 0a5050eb3c | |||
| 7c430c5ed0 | |||
| 93d62860e9 | |||
| 5bcd719866 | |||
| e1743cc837 | |||
| 52ee1ab5eb | |||
| fcc556df37 | |||
| 5c0ec9d06d | |||
| ac2f0cece6 | |||
| fbaa1968b7 | |||
| 13d5528a5f | |||
| b5406d276d | |||
| 0f89383d98 | |||
| 10daefc6f4 | |||
| dc7b764d4d | |||
| 82d90a4096 | |||
| 7468bcb80b | |||
| bd4a4d390c | |||
| 94fca76a08 | |||
| 1c8c083404 | |||
| 0f2de12273 | |||
| 1bbc67ef6c | |||
| 637817e3ba | |||
| 86182a9415 | |||
| 15ecc6f366 | |||
| 53b2254ea7 | |||
| 6336b6e89e | |||
| fdf17d729f | |||
| 85776c0d37 | |||
| 02cf958a07 | |||
| 8fe4295a06 | |||
| dcb8e0767f | |||
| 21b77ad5c2 | |||
| 2f5dba488e | |||
| 7e86eacf30 | |||
| 15c1fe3929 | |||
| 428f2b5636 | |||
| 199ac26986 | |||
| 5f70d615a5 | |||
| 06d0955074 | |||
| a22e0699be | |||
| 28ff7c3a66 | |||
| 59ae8adb3c | |||
| c0b78478a0 | |||
| 59fa40ec0e | |||
| a1f7f5d4d0 | |||
| 20687aa5f6 | |||
| fc52b81d52 | |||
| ae1aee2b6c | |||
| 423242017e | |||
| 95c5df5958 | |||
| 2cb907a836 | |||
| 2f2fd465a4 | |||
| 6da355c3e1 | |||
| f2106692b1 | |||
| 4557881cf8 | |||
| af0ad6561c | |||
| 35125d2513 | |||
| 87f5c6e5b7 | |||
| a8a750ab05 | |||
| 13a95ae499 | |||
| da9f4dfcf4 | |||
| ac4318a2fa | |||
| bb2fa6f63f | |||
| ba59ef4950 | |||
| 8b93541522 | |||
| 7b22d59b4a | |||
| 798bca62c6 | |||
| 8218c4b60b | |||
| 2430f52bb9 | |||
| 944098a4e0 | |||
| 2ce0485e6f | |||
| a08cbc02e7 | |||
| 55027132f3 | |||
| 09e175f02c | |||
| 5c5b1183c8 | |||
| f04142ea28 | |||
| aaedefb92e | |||
| d54dfecb00 | |||
| 4b8d926062 | |||
| 74c84501ed | |||
| 4581b79bbd | |||
| 2be8847ef6 | |||
| cb2ad9abf2 | |||
| 73c8593077 | |||
| ac75079e21 | |||
| 5390fb37d2 | |||
| 8d7e694849 | |||
| 5fdab52dd7 | |||
| 541bedd1a9 | |||
| 98e18a64aa | |||
| 0a45bff472 | |||
| 263524d381 | |||
| 52c59cf0ce | |||
| c5f8edfe03 | |||
| 69f0aa899d | |||
| e7cd0bcc5a | |||
| ade6c45275 | |||
| 9eafd10fcd | |||
| 3436c027f2 | |||
| 6a8749e65a | |||
| 1a5bebd927 | |||
| 83155e8fbe | |||
| 6d6f875345 | |||
| a4fe51da3b | |||
| ee5a5352fd | |||
| 9cb2195e61 | |||
| 15213ec212 | |||
| 9171c76bb4 | |||
| 64fb1f2620 | |||
| f49eaf8bf2 | |||
| f701ce08f9 | |||
| 1cc0e4173d | |||
| d4ae7988da | |||
| 5ac14f633a | |||
| 9918b748be | |||
| 6ecac8e71a | |||
| 823adb2319 | |||
| 21e74c2d2e | |||
| 6c5a05ad49 | |||
| 192ff61f5d | |||
| 935c1018da | |||
| 78a6291666 | |||
| 53b6f522a5 |
@@ -1,3 +1,290 @@
|
||||
<a name="1.0.0rc7"></a>
|
||||
# 1.0.0rc7 rc-generation (2012-04-30)
|
||||
|
||||
## Features
|
||||
|
||||
- **$parse:** CSP compatibility
|
||||
([2b87c814](https://github.com/angular/angular.js/commit/2b87c814ab70eaaff6359ce1a118f348c8bd2197),
|
||||
[#893](https://github.com/angular/angular.js/issues/893))
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **jqlite:**
|
||||
- correctly reset event properties in IE8
|
||||
([a18926f9](https://github.com/angular/angular.js/commit/a18926f986166048a21097636f03ab29f107b154))
|
||||
- mouseenter on FF no longer throws exceptions
|
||||
([43d15f83](https://github.com/angular/angular.js/commit/43d15f830f9d419c41c41f0682e47e86839e3917))
|
||||
|
||||
|
||||
## Docs
|
||||
|
||||
- Tutorial has been finally updated to AngularJS v1.0! Check it out and provide feedback to make it
|
||||
even better: <http://docs.angularjs.org/tutorial>
|
||||
- <http://docs-next.angularjs.org> now redirects to <http://docs.angularjs.org>
|
||||
|
||||
|
||||
|
||||
<a name="v1.0.0rc6"></a>
|
||||
# v1.0.0rc6 runny-nose (2012-04-20)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **select:** properly handle empty & unknown options without ngOptions
|
||||
([904b69c7](https://github.com/angular/angular.js/commit/904b69c745ea4afc1d6ecd2a5f3138c6f947b157))
|
||||
- **compiler:** reading comment throws error in ie
|
||||
([46bb08a9](https://github.com/angular/angular.js/commit/46bb08a9d0780fafef6dc5c1140c71912462887a))
|
||||
- **document:** accidental clobbering of document.getAttribute
|
||||
([eafe15f5](https://github.com/angular/angular.js/commit/eafe15f54c686d5c83f777fd319f4c568e209432),
|
||||
[#877](https://github.com/angular/angular.js/issues/877))
|
||||
- **script:** Incorrectly reading script text on ie
|
||||
([94dd6857](https://github.com/angular/angular.js/commit/94dd68570952f6f31abfa351b1159afcd3588a57))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$resource:** support HTTP PATCH method
|
||||
([e61fd1b4](https://github.com/angular/angular.js/commit/e61fd1b43a55496c11c63da7ca2fc05b88d44043),
|
||||
[#887](https://github.com/angular/angular.js/issues/887))
|
||||
- **jquery:** jquery 1.7.2 support
|
||||
([8ebe5ccd](https://github.com/angular/angular.js/commit/8ebe5ccd9ace7807bedc7317d605370fe82b773d))
|
||||
|
||||
|
||||
|
||||
<a name="1.0.0rc5"></a>
|
||||
# 1.0.0rc5 reality-distortion (2012-04-12)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$location:** properly rewrite urls in html5 mode with base url set + don't rewrite links to
|
||||
different base paths
|
||||
([6d7e7fde](https://github.com/angular/angular.js/commit/6d7e7fdea6c3d6551ff40c150aa42e1375d2cb5f),
|
||||
[0a5050eb](https://github.com/angular/angular.js/commit/0a5050eb3c1f1ed84134f23a44b97a7261114060))
|
||||
- **e2eRunner:** $browser.location should delegate to apps $location
|
||||
([df72852f](https://github.com/angular/angular.js/commit/df72852f3496d7640bb4f70837338e464b7ed69f))
|
||||
- **input.radio:** support 2-way binding in a repeater
|
||||
([93d62860](https://github.com/angular/angular.js/commit/93d62860e988a09fb64e594f50f6cd55a1fc5748),
|
||||
[#869](https://github.com/angular/angular.js/issues/869))
|
||||
- **ngBindHtml:** clear contents when model is falsy
|
||||
([10daefc6](https://github.com/angular/angular.js/commit/10daefc6f466a21d9418437666461c80cf24fcfe),
|
||||
[#864](https://github.com/angular/angular.js/issues/864))
|
||||
- lots of doc fixes
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$http:** expose the defaults config as $http.defaults
|
||||
([dceafd32](https://github.com/angular/angular.js/commit/dceafd32ee140c8af5c7a0ca6cb808395fffeed3))
|
||||
- **docs:** steps 0-4 of the Tutorial have been updated and improved
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- `ng-ext-link` directive was removed because it's unnecessary
|
||||
([6d7e7fde](https://github.com/angular/angular.js/commit/6d7e7fdea6c3d6551ff40c150aa42e1375d2cb5f))
|
||||
|
||||
apps that relied on ng-ext-link should simply replace it with `target="_self"`
|
||||
|
||||
- `$browser.addCss` was removed - it was never meant to be a public api
|
||||
([13d5528a](https://github.com/angular/angular.js/commit/13d5528a5f5a2f0feee5c742788a914d2371841e))
|
||||
|
||||
apps the depend on this functionality should write a simple utility function specific to the app
|
||||
(see this diff for hints).
|
||||
|
||||
- `$browser.addJs` method was removed - it was never meant to be a public api
|
||||
([fbaa1968](https://github.com/angular/angular.js/commit/fbaa1968b7c596ccb63ea8b4be1d3bd92eda50d8))
|
||||
|
||||
apps that depended on this functionality should either use many of the existing script loaders or
|
||||
create a simple helper method specific to the app.
|
||||
|
||||
- `$sanitize` service, `ngBindHtml` directive and `linky` filter were moved to the `ngSanitize` module
|
||||
([5bcd7198](https://github.com/angular/angular.js/commit/5bcd7198664dca2bf85ddf8b3a89f417cd4e4796))
|
||||
|
||||
apps that depend on any of these will need to load `angular-sanitize.js` and include `ngSanitize`
|
||||
in their dependency list: `var myApp = angular.module('myApp', ['ngSanitize']);`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="1.0.0rc4"></a>
|
||||
# 1.0.0rc4 insomnia-induction (2012-04-05)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:** relax the restriction that directives can not add siblings
|
||||
([7e86eacf](https://github.com/angular/angular.js/commit/7e86eacf301934335c22908ec6dbd1a083d88fab))
|
||||
- **$location:** search setter should not double-encode the value
|
||||
([59fa40ec](https://github.com/angular/angular.js/commit/59fa40ec0e851759d35fb0ea5fd01019d1403049),
|
||||
[#751](https://github.com/angular/angular.js/issues/751))
|
||||
- **$q:** $q.reject should forward callbacks if missing
|
||||
([c0b78478](https://github.com/angular/angular.js/commit/c0b78478a0e64942a69aba7c1bfa4eb01c0e9a5e),
|
||||
[#845](https://github.com/angular/angular.js/issues/845))
|
||||
- **build:** move `'use strict';` flag into the angular closure
|
||||
([637817e3](https://github.com/angular/angular.js/commit/637817e3ba48d149e7a9628533d21e81c650d988))
|
||||
- **Directives**:
|
||||
- **ngModel:** update model on each key stroke (revert ngModelInstant)
|
||||
([06d09550](https://github.com/angular/angular.js/commit/06d0955074f79de553cc34fbf945045dc458e064))
|
||||
- **booleanAttrs:** always convert the model to boolean before setting the element property
|
||||
([dcb8e076](https://github.com/angular/angular.js/commit/dcb8e0767fbf0a7a55f3b0045fd01b2532ea5441))
|
||||
- **form:** preperly clean up when invalid widget is removed
|
||||
([21b77ad5](https://github.com/angular/angular.js/commit/21b77ad5c231ab0e05eb89f22005f7ed8d40a6c1))
|
||||
- **ngHref:** copy even if no binding
|
||||
([2f5dba48](https://github.com/angular/angular.js/commit/2f5dba488e855bcdbb9304aa809efcb9de7b43e9))
|
||||
- **ngInclude:** fire $includeContentLoaded on proper (child) scope
|
||||
([199ac269](https://github.com/angular/angular.js/commit/199ac269869a57bb63d60c9b3f510d546bf0c9b2))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$http:** add `withCredentials` config option
|
||||
([86182a94](https://github.com/angular/angular.js/commit/86182a9415b9209662b16c25c180b958ba7e6cf9))
|
||||
- **$route:** allow chaining of whens and otherwise
|
||||
([15ecc6f3](https://github.com/angular/angular.js/commit/15ecc6f3668885ebc5c7130dd34e00059ddf79ae))
|
||||
- **ngInclude:** allow ngInclude as css class
|
||||
([428f2b56](https://github.com/angular/angular.js/commit/428f2b563663315df4f235ca19cef4bdcf82e2ab))
|
||||
|
||||
|
||||
## Docs
|
||||
- reintroduced the tutorial docs - currently only steps 0-3 are up to date and the code is not split
|
||||
up into step specific commits yet. See
|
||||
[this branch](https://github.com/angular/angular-phonecat/tree/v1.0-update) instead.
|
||||
- various other doc fixes
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
We removed two useless features:
|
||||
|
||||
- $routeProvider.when used to return the route definition object but now it returns self
|
||||
([15ecc6f3](https://github.com/angular/angular.js/commit/15ecc6f3668885ebc5c7130dd34e00059ddf79ae))
|
||||
- ngInclude does not have scope attribute anymore
|
||||
([5f70d615](https://github.com/angular/angular.js/commit/5f70d615a5f7e102424c6adc15d7a6f697870b6e))
|
||||
- ngModelInstant directive is no more and ngModel behaves just as ngModelInstant used to. This
|
||||
doesn't really break anything, just remember to remove all ngModelInstant references from your
|
||||
template as they serve no purpose now.
|
||||
([06d09550](https://github.com/angular/angular.js/commit/06d0955074f79de553cc34fbf945045dc458e064))
|
||||
|
||||
|
||||
|
||||
<a name="1.0.0rc3"></a>
|
||||
# 1.0.0rc3 barefoot-telepathy (2012-03-29)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- properly clone attr.$observers in ng-repeat
|
||||
([f2106692](https://github.com/angular/angular.js/commit/f2106692b1ebf00aa5f8b2accd75f014b6cd4faa))
|
||||
- create new (isolate) scopes for directives on root elements
|
||||
([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787),
|
||||
[#817](https://github.com/angular/angular.js/issues/817))
|
||||
- **angular.forEach:** should ignore prototypically inherited properties
|
||||
([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61),
|
||||
[#813](https://github.com/angular/angular.js/issues/813))
|
||||
- **initialization:** use jQuery#ready for initialization if available
|
||||
([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d),
|
||||
[#818](https://github.com/angular/angular.js/issues/818))
|
||||
- **$q:** resolve all of nothing to nothing
|
||||
([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:** do not interpolate boolean attribute directives, rather evaluate them
|
||||
([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
|
||||
- **$controller:** support controller registration via $controllerProvider
|
||||
([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
|
||||
- **$http:**
|
||||
- make the `transformRequest` and `transformResponse` default to an array
|
||||
([a8a750ab](https://github.com/angular/angular.js/commit/a8a750ab05bdff73ba3af0b98f3f284ff8d1e743))
|
||||
- added `params` parameter
|
||||
([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
|
||||
- **TzDate:** add support for toISOString method
|
||||
([da9f4dfc](https://github.com/angular/angular.js/commit/da9f4dfcf4f3d0c21821d8474ac0bb19a3c51415))
|
||||
- **jqLite:** make injector() and scope() work with the document object
|
||||
([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
|
||||
- **ngValue:** directive that allows radio inputs to have non string values
|
||||
([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b),
|
||||
[#816](https://github.com/angular/angular.js/issues/816))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- `$resource`, `$cookies` and `$cookieStore` services are now distributed as separate modules, see
|
||||
`angular-resource.js` and `angular-cookies.js`.
|
||||
([798bca62](https://github.com/angular/angular.js/commit/798bca62c6f64775b85deda3713e7b6bcc7a4b4d),
|
||||
[7b22d59b](https://github.com/angular/angular.js/commit/7b22d59b4a16d5c50c2eee054178ba17f8038880))
|
||||
- angular.fromJson doesn't deserialize date strings into date objects.
|
||||
([ac4318a2](https://github.com/angular/angular.js/commit/ac4318a2fa5c6d306dbc19466246292a81767fca))
|
||||
- angular.toJson always use native JSON.parse and JSON.stringify - this might break code that
|
||||
consumes the output in whitespace-sensitive way
|
||||
([35125d25](https://github.com/angular/angular.js/commit/35125d25137ac2da13ed1ca3e652ec8f2c945053))
|
||||
- IE7 and older have are now required to polyfill the JSON global object
|
||||
([87f5c6e5](https://github.com/angular/angular.js/commit/87f5c6e5b716100e203ec59c5874c3e927f83fa0))
|
||||
- boolean attr directives (ng-disabled, ng-required, etc) are evaluated rather than interpolated
|
||||
([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
|
||||
- `ng-bind-attr` directive removed
|
||||
([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
|
||||
- any app that depends on $sniffer service should use Modernizr instead
|
||||
([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))
|
||||
|
||||
|
||||
|
||||
<a name="1.0.0rc2"></a>
|
||||
# 1.0.0rc2 silence-absorption (2012-03-20)
|
||||
|
||||
## Features
|
||||
|
||||
- **$route:** when matching consider trailing slash as optional
|
||||
([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6))
|
||||
- **jqLite:** add .controller() method
|
||||
([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
|
||||
- **scope.$eval:** allow passing locals to the expression
|
||||
([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
|
||||
- **input[type=radio]:** allow the value attribute to be interpolated
|
||||
([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$http:** don't send Content-Type header when no data
|
||||
([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb),
|
||||
[#749](https://github.com/angular/angular.js/issues/749))
|
||||
- **$resource:** support escaping of ':' in resource url
|
||||
([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
|
||||
- **$compile:**
|
||||
- don't touch static element attributes
|
||||
([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
|
||||
- merge interpolated css class when replacing an element
|
||||
([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
|
||||
- allow transclusion of root elements
|
||||
([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
|
||||
- **$log:** avoid console.log.apply calls in IE
|
||||
([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca),
|
||||
[#805](https://github.com/angular/angular.js/issues/805))
|
||||
- **json:** added support for iso8061 timezone
|
||||
([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
|
||||
- **e2e runner:** fix typo that caused errors on IE8
|
||||
([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b),
|
||||
[#806](https://github.com/angular/angular.js/issues/806))
|
||||
- **directives:**
|
||||
- **select:** multiselect failes to update view on selection insert
|
||||
([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
|
||||
- **ngForm:** alias name||ngForm
|
||||
([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
|
||||
- **ngView:** publish the controller
|
||||
([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
|
||||
- **ngRepeat:** correct variable reference in error message
|
||||
([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
|
||||
- various doc fixes (some contributed by Daniel Zen)
|
||||
|
||||
|
||||
|
||||
<a name="1.0.0rc1"></a>
|
||||
# 1.0.0rc1 moiré-vision (2012-03-13)
|
||||
|
||||
@@ -63,9 +350,9 @@ docs. The biggest improvements and changes are listed below.
|
||||
So instead of `{{ someRawHtml | html }}` use `<div ng-bind-html="someRawHtml"></div>` and
|
||||
instead of `{{ someRawHtml | html:"unsafe" }}` use `<div ng-bind-html-unsafe="someRawHtml"></div>`.
|
||||
Please check out the
|
||||
[ng-bind-html](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-bind-html)
|
||||
[ng-bind-html](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngBindHtml)
|
||||
and
|
||||
[ng-bind-html-unsafe](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-bind-html-unsafe)
|
||||
[ng-bind-html-unsafe](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngBindHtmlUnsafe)
|
||||
directive docs.
|
||||
|
||||
- Custom markup has been used by developers only to switch from `{{ }}` markup to `(( ))` or
|
||||
@@ -214,13 +501,13 @@ behavior and migrate your controllers one at a time: <https://gist.github.com/16
|
||||
|
||||
## New directives:
|
||||
|
||||
- [ng-mouseleave](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-mouseleave)
|
||||
- [ng-mousemove](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-mousemove)
|
||||
- [ng-mouseover](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-mouseover)
|
||||
- [ng-mouseup](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-mouseup)
|
||||
- [ng-mousedown](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-mousedown)
|
||||
- [ng-dblclick](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-dblclick)
|
||||
- [ng-model-instant](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ng-model-instant)
|
||||
- [ng-mouseleave](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngMouseleave)
|
||||
- [ng-mousemove](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngMousemove)
|
||||
- [ng-mouseover](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngMouseover)
|
||||
- [ng-mouseup](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngMouseup)
|
||||
- [ng-mousedown](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngMousedown)
|
||||
- [ng-dblclick](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngDblclick)
|
||||
- [ng-model-instant](http://docs-next.angularjs.org/api/angular.module.ng.$compileProvider.directive.ngModelInstant)
|
||||
|
||||
|
||||
## $injector / modules
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
|
||||
Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -2,7 +2,10 @@ require 'yaml'
|
||||
include FileUtils
|
||||
|
||||
content = File.open('angularFiles.js', 'r') {|f| f.read }
|
||||
files = eval(content.gsub(/\};(\s|\S)*/, '}').gsub(/angularFiles = /, '').gsub(/:/, '=>'));
|
||||
files = eval(content.gsub(/\};(\s|\S)*/, '}').
|
||||
gsub(/angularFiles = /, '').
|
||||
gsub(/:/, '=>').
|
||||
gsub(/\/\//, '#'));
|
||||
|
||||
BUILD_DIR = 'build'
|
||||
|
||||
@@ -35,37 +38,23 @@ end
|
||||
desc 'Compile Scenario'
|
||||
task :compile_scenario => :init do
|
||||
|
||||
deps = [
|
||||
concat_file('angular-scenario.js', [
|
||||
'lib/jquery/jquery.js',
|
||||
'src/scenario/angular.prefix',
|
||||
'src/ngScenario/angular.prefix',
|
||||
files['angularSrc'],
|
||||
files['angularScenario'],
|
||||
'src/scenario/angular.suffix',
|
||||
]
|
||||
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
|
||||
File.open(path_to('angular-scenario.js'), 'w') do |f|
|
||||
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
|
||||
'src/ngScenario/angular.suffix',
|
||||
], gen_css('css/angular.css') + "\n" + gen_css('css/angular-scenario.css'))
|
||||
end
|
||||
|
||||
desc 'Compile JSTD Scenario Adapter'
|
||||
task :compile_jstd_scenario_adapter => :init do
|
||||
|
||||
deps = [
|
||||
'src/jstd-scenario-adapter/angular.prefix',
|
||||
'src/jstd-scenario-adapter/Adapter.js',
|
||||
'src/jstd-scenario-adapter/angular.suffix',
|
||||
]
|
||||
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
|
||||
File.open(path_to('jstd-scenario-adapter.js'), 'w') do |f|
|
||||
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
|
||||
end
|
||||
concat_file('jstd-scenario-adapter.js', [
|
||||
'src/ngScenario/jstd-scenario-adapter/angular.prefix',
|
||||
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
|
||||
'src/ngScenario/jstd-scenario-adapter/angular.suffix',
|
||||
])
|
||||
|
||||
# TODO(vojta) use jstd configuration when implemented
|
||||
# (instead of including jstd-adapter-config.js)
|
||||
@@ -80,67 +69,45 @@ end
|
||||
desc 'Compile JavaScript'
|
||||
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter] do
|
||||
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
files['angularSrc'],
|
||||
'src/angular.suffix',
|
||||
]
|
||||
concat_file('angular.js', [
|
||||
'src/angular.prefix',
|
||||
files['angularSrc'],
|
||||
'src/angular.suffix',
|
||||
], gen_css('css/angular.css', true))
|
||||
|
||||
File.open(path_to('angular.js'), 'w') do |f|
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
FileUtils.cp_r 'src/ngLocale', path_to('i18n')
|
||||
|
||||
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')})
|
||||
|
||||
FileUtils.cp_r 'i18n/locale', path_to('i18n')
|
||||
|
||||
File.open(path_to('angular-loader.js'), 'w') do |f|
|
||||
concat = 'cat ' + [
|
||||
concat_file('angular-loader.js', [
|
||||
'src/loader.prefix',
|
||||
'src/loader.js',
|
||||
'src/loader.suffix'].flatten.join(' ')
|
||||
'src/loader.suffix'])
|
||||
|
||||
content = %x{#{concat}}.
|
||||
gsub('"NG_VERSION_FULL"', NG_VERSION.full).
|
||||
gsub(/^\s*['"]use strict['"];?\s*$/, '') # remove all file-specific strict mode flags
|
||||
|
||||
f.write(content)
|
||||
end
|
||||
concat_module('sanitize', [
|
||||
'src/ngSanitize/sanitize.js',
|
||||
'src/ngSanitize/directive/ngBindHtml.js',
|
||||
'src/ngSanitize/filter/linky.js'])
|
||||
|
||||
%x(java -jar lib/closure-compiler/compiler.jar \
|
||||
--compilation_level SIMPLE_OPTIMIZATIONS \
|
||||
--language_in ECMASCRIPT5_STRICT \
|
||||
--js #{path_to('angular-loader.js')} \
|
||||
--js_output_file #{path_to('angular-loader.min.js')})
|
||||
concat_module('resource', ['src/ngResource/resource.js'])
|
||||
concat_module('cookies', ['src/ngCookies/cookies.js'])
|
||||
|
||||
|
||||
FileUtils.cp 'src/ngMock/angular-mocks.js', path_to('angular-mocks.js')
|
||||
|
||||
closure_compile('angular.js')
|
||||
closure_compile('angular-cookies.js')
|
||||
closure_compile('angular-loader.js')
|
||||
closure_compile('angular-resource.js')
|
||||
closure_compile('angular-sanitize.js')
|
||||
|
||||
end
|
||||
|
||||
|
||||
desc 'Generate docs'
|
||||
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)
|
||||
rewrite_file(path_to('docs/.htaccess')) do |content|
|
||||
content.sub!('"NG_VERSION_FULL"', NG_VERSION.full)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -153,103 +120,61 @@ task :package => [:clean, :compile, :docs] do
|
||||
FileUtils.rm_r(path_to('pkg'), :force => true)
|
||||
FileUtils.mkdir_p(pkg_dir)
|
||||
|
||||
['src/angular-mocks.js',
|
||||
path_to('angular.js'),
|
||||
path_to('angular-loader.js'),
|
||||
[ path_to('angular.js'),
|
||||
path_to('angular.min.js'),
|
||||
path_to('angular-loader.js'),
|
||||
path_to('angular-loader.min.js'),
|
||||
path_to('angular-mocks.js'),
|
||||
path_to('angular-cookies.js'),
|
||||
path_to('angular-cookies.min.js'),
|
||||
path_to('angular-resource.js'),
|
||||
path_to('angular-resource.min.js'),
|
||||
path_to('angular-sanitize.js'),
|
||||
path_to('angular-sanitize.min.js'),
|
||||
path_to('angular-scenario.js'),
|
||||
path_to('jstd-scenario-adapter.js'),
|
||||
path_to('jstd-scenario-adapter-config.js'),
|
||||
].each do |src|
|
||||
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{NG_VERSION.full}\\1")
|
||||
dest = src.gsub(/^.*\//, '').gsub(/((\.min)?\.js)$/, "-#{NG_VERSION.full}\\1")
|
||||
FileUtils.cp(src, pkg_dir + '/' + dest)
|
||||
end
|
||||
|
||||
FileUtils.cp_r path_to('i18n'), "#{pkg_dir}/i18n-#{NG_VERSION.full}"
|
||||
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{NG_VERSION.full}"
|
||||
|
||||
File.open("#{pkg_dir}/angular-mocks-#{NG_VERSION.full}.js", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('"NG_VERSION_FULL"', NG_VERSION.full)
|
||||
end
|
||||
|
||||
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-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
rewrite_file("#{pkg_dir}/angular-mocks-#{NG_VERSION.full}.js") do |content|
|
||||
content.sub!('"NG_VERSION_FULL"', NG_VERSION.full)
|
||||
end
|
||||
|
||||
|
||||
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.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
[ "#{pkg_dir}/docs-#{NG_VERSION.full}/index.html",
|
||||
"#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq.html",
|
||||
"#{pkg_dir}/docs-#{NG_VERSION.full}/index-nocache.html",
|
||||
"#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-nocache.html",
|
||||
"#{pkg_dir}/docs-#{NG_VERSION.full}/index-debug.html",
|
||||
"#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-debug.html"
|
||||
].each do |src|
|
||||
rewrite_file(src) do |content|
|
||||
content.sub!('angular.js', "angular-#{NG_VERSION.full}.js").
|
||||
sub!('angular-resource.js', "angular-resource-#{NG_VERSION.full}.js").
|
||||
sub!('angular-cookies.js', "angular-cookies-#{NG_VERSION.full}.js").
|
||||
sub!('angular-sanitize.js', "angular-sanitize-#{NG_VERSION.full}.js")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
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-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
rewrite_file("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html") do |content|
|
||||
content.sub!('angular-scenario.js', "angular-scenario-#{NG_VERSION.full}.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").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
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").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
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").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
[ "#{pkg_dir}/docs-#{NG_VERSION.full}/appcache.manifest",
|
||||
"#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest"
|
||||
].each do |src|
|
||||
rewrite_file(src) do |content|
|
||||
content.sub!('../angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub!('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -336,3 +261,61 @@ end
|
||||
def path_to(filename)
|
||||
return File.join(BUILD_DIR, *filename)
|
||||
end
|
||||
|
||||
|
||||
def closure_compile(filename)
|
||||
puts "Compiling #{filename} ..."
|
||||
|
||||
min_path = path_to(filename.gsub(/\.js$/, '.min.js'))
|
||||
|
||||
%x(java -jar lib/closure-compiler/compiler.jar \
|
||||
--compilation_level SIMPLE_OPTIMIZATIONS \
|
||||
--language_in ECMASCRIPT5_STRICT \
|
||||
--js #{path_to(filename)} \
|
||||
--js_output_file #{min_path})
|
||||
|
||||
rewrite_file(min_path) do |content|
|
||||
content.sub!("'use strict';", "").
|
||||
sub!(/\(function\([^)]*\)\{/, "\\0'use strict';")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def concat_file(filename, deps, footer='')
|
||||
puts "Building #{filename} ..."
|
||||
File.open(path_to(filename), 'w') do |f|
|
||||
concat = 'cat ' + deps.flatten.join(' ')
|
||||
|
||||
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
|
||||
sub(/\(function\([^)]*\)\s*\{/, "\\0\n'use strict';") # add single strict mode flag
|
||||
|
||||
f.write(content)
|
||||
f.write(footer)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def concat_module(name, files)
|
||||
concat_file('angular-' + name + '.js', ['src/module.prefix'] + files + ['src/module.suffix'])
|
||||
end
|
||||
|
||||
|
||||
def rewrite_file(filename)
|
||||
File.open(filename, File::RDWR) do |f|
|
||||
content = f.read
|
||||
|
||||
content = yield content
|
||||
|
||||
raise "File rewrite failed - No content!" unless content
|
||||
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write content
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,81 +3,108 @@ angularFiles = {
|
||||
'src/Angular.js',
|
||||
'src/loader.js',
|
||||
'src/AngularPublic.js',
|
||||
'src/JSON.js',
|
||||
'src/Injector.js',
|
||||
'src/Resource.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/service/anchorScroll.js',
|
||||
'src/service/browser.js',
|
||||
'src/service/cacheFactory.js',
|
||||
'src/service/compiler.js',
|
||||
'src/service/controller.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/filter.js',
|
||||
'src/service/filter/filter.js',
|
||||
'src/service/filter/filters.js',
|
||||
'src/service/filter/limitTo.js',
|
||||
'src/service/filter/orderBy.js',
|
||||
'src/service/interpolate.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/parse.js',
|
||||
'src/service/q.js',
|
||||
'src/service/route.js',
|
||||
'src/service/routeParams.js',
|
||||
'src/service/scope.js',
|
||||
'src/service/sanitize.js',
|
||||
'src/service/sniffer.js',
|
||||
'src/service/window.js',
|
||||
'src/service/http.js',
|
||||
'src/service/httpBackend.js',
|
||||
'src/service/locale.js',
|
||||
'src/directive/directives.js',
|
||||
'src/directive/a.js',
|
||||
'src/directive/booleanAttrDirs.js',
|
||||
'src/directive/form.js',
|
||||
'src/directive/input.js',
|
||||
'src/directive/ngBind.js',
|
||||
'src/directive/ngClass.js',
|
||||
'src/directive/ngCloak.js',
|
||||
'src/directive/ngController.js',
|
||||
'src/directive/ngEventDirs.js',
|
||||
'src/directive/ngInclude.js',
|
||||
'src/directive/ngInit.js',
|
||||
'src/directive/ngNonBindable.js',
|
||||
'src/directive/ngPluralize.js',
|
||||
'src/directive/ngRepeat.js',
|
||||
'src/directive/ngShowHide.js',
|
||||
'src/directive/ngStyle.js',
|
||||
'src/directive/ngSwitch.js',
|
||||
'src/directive/ngTransclude.js',
|
||||
'src/directive/ngView.js',
|
||||
'src/directive/script.js',
|
||||
'src/directive/select.js',
|
||||
'src/directive/style.js'
|
||||
|
||||
'src/auto/injector.js',
|
||||
|
||||
'src/ng/anchorScroll.js',
|
||||
'src/ng/browser.js',
|
||||
'src/ng/cacheFactory.js',
|
||||
'src/ng/compiler.js',
|
||||
'src/ng/controller.js',
|
||||
'src/ng/defer.js',
|
||||
'src/ng/document.js',
|
||||
'src/ng/exceptionHandler.js',
|
||||
'src/ng/interpolate.js',
|
||||
'src/ng/location.js',
|
||||
'src/ng/log.js',
|
||||
'src/ng/parse.js',
|
||||
'src/ng/q.js',
|
||||
'src/ng/route.js',
|
||||
'src/ng/routeParams.js',
|
||||
'src/ng/rootScope.js',
|
||||
'src/ng/sniffer.js',
|
||||
'src/ng/window.js',
|
||||
'src/ng/http.js',
|
||||
'src/ng/httpBackend.js',
|
||||
'src/ng/locale.js',
|
||||
|
||||
'src/ng/filter.js',
|
||||
'src/ng/filter/filter.js',
|
||||
'src/ng/filter/filters.js',
|
||||
'src/ng/filter/limitTo.js',
|
||||
'src/ng/filter/orderBy.js',
|
||||
|
||||
'src/ng/directive/directives.js',
|
||||
'src/ng/directive/a.js',
|
||||
'src/ng/directive/booleanAttrs.js',
|
||||
'src/ng/directive/form.js',
|
||||
'src/ng/directive/input.js',
|
||||
'src/ng/directive/ngBind.js',
|
||||
'src/ng/directive/ngClass.js',
|
||||
'src/ng/directive/ngCloak.js',
|
||||
'src/ng/directive/ngController.js',
|
||||
'src/ng/directive/ngCsp.js',
|
||||
'src/ng/directive/ngEventDirs.js',
|
||||
'src/ng/directive/ngInclude.js',
|
||||
'src/ng/directive/ngInit.js',
|
||||
'src/ng/directive/ngNonBindable.js',
|
||||
'src/ng/directive/ngPluralize.js',
|
||||
'src/ng/directive/ngRepeat.js',
|
||||
'src/ng/directive/ngShowHide.js',
|
||||
'src/ng/directive/ngStyle.js',
|
||||
'src/ng/directive/ngSwitch.js',
|
||||
'src/ng/directive/ngTransclude.js',
|
||||
'src/ng/directive/ngView.js',
|
||||
'src/ng/directive/script.js',
|
||||
'src/ng/directive/select.js',
|
||||
'src/ng/directive/style.js'
|
||||
],
|
||||
|
||||
'angularSrcModules': [
|
||||
'src/ngCookies/cookies.js',
|
||||
'src/ngResource/resource.js',
|
||||
'src/ngSanitize/sanitize.js',
|
||||
'src/ngSanitize/directive/ngBindHtml.js',
|
||||
'src/ngSanitize/filter/linky.js',
|
||||
'src/ngMock/angular-mocks.js'
|
||||
],
|
||||
|
||||
'angularScenario': [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js'
|
||||
'src/ngScenario/Scenario.js',
|
||||
'src/ngScenario/Application.js',
|
||||
'src/ngScenario/Describe.js',
|
||||
'src/ngScenario/Future.js',
|
||||
'src/ngScenario/ObjectModel.js',
|
||||
'src/ngScenario/Describe.js',
|
||||
'src/ngScenario/Runner.js',
|
||||
'src/ngScenario/SpecRunner.js',
|
||||
'src/ngScenario/dsl.js',
|
||||
'src/ngScenario/matchers.js',
|
||||
'src/ngScenario/output/Html.js',
|
||||
'src/ngScenario/output/Json.js',
|
||||
'src/ngScenario/output/Xml.js',
|
||||
'src/ngScenario/output/Object.js'
|
||||
],
|
||||
|
||||
'angularTest': [
|
||||
'test/testabilityPatch.js',
|
||||
'test/matchers.js',
|
||||
'test/ngScenario/*.js',
|
||||
'test/ngScenario/output/*.js',
|
||||
'test/ngScenario/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/auto/*.js',
|
||||
'test/ng/*.js',
|
||||
'test/ng/directive/*.js',
|
||||
'test/ng/filter/*.js',
|
||||
'test/ngCookies/*.js',
|
||||
'test/ngResource/*.js',
|
||||
'test/ngSanitize/*.js',
|
||||
'test/ngSanitize/directive/*.js',
|
||||
'test/ngSanitize/filter/*.js',
|
||||
'test/ngMock/*.js'
|
||||
],
|
||||
|
||||
'jstd': [
|
||||
@@ -86,28 +113,19 @@ angularFiles = {
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_remove.js',
|
||||
'@angularSrc',
|
||||
'src/publishExternalApis.js',
|
||||
'@angularSrcModules',
|
||||
'@angularScenario',
|
||||
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
|
||||
'@angularTest',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'test/matchers.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'test/service/filter/*.js',
|
||||
'test/directive/*.js',
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdExclude': [
|
||||
'test/jquery_alias.js',
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js'
|
||||
'src/ngScenario/angular-bootstrap.js'
|
||||
],
|
||||
|
||||
'jstdScenario': [
|
||||
@@ -117,28 +135,39 @@ angularFiles = {
|
||||
'build/docs/docs-scenario.js'
|
||||
],
|
||||
|
||||
'jstdMocks': [
|
||||
"jstdModules": [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'build/angular.js',
|
||||
'src/angular-mocks.js',
|
||||
'src/ngMock/angular-mocks.js',
|
||||
'src/ngCookies/cookies.js',
|
||||
'src/ngResource/resource.js',
|
||||
'src/ngSanitize/sanitize.js',
|
||||
'src/ngSanitize/directive/ngBindHtml.js',
|
||||
'src/ngSanitize/filter/linky.js',
|
||||
'test/matchers.js',
|
||||
'test/angular-mocksSpec.js'
|
||||
'test/ngMock/*.js',
|
||||
'test/ngCookies/*.js',
|
||||
'test/ngResource/*.js',
|
||||
'test/ngSanitize/*.js',
|
||||
'test/ngSanitize/directive/*.js',
|
||||
'test/ngSanitize/filter/*.js'
|
||||
],
|
||||
|
||||
'jstdPerf': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'angularSrc',
|
||||
'src/angular-mocks.js',
|
||||
'@angularSrc',
|
||||
'@angularSrcModules',
|
||||
'src/ngMock/angular-mocks.js',
|
||||
'perf/data/*.js',
|
||||
'perf/testUtils.js',
|
||||
'perf/*.js'
|
||||
],
|
||||
|
||||
'jstdPerfExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js'
|
||||
'src/ng/angular-bootstrap.js',
|
||||
'src/ngScenario/angular-bootstrap.js'
|
||||
],
|
||||
|
||||
'jstdJquery': [
|
||||
@@ -147,41 +176,49 @@ angularFiles = {
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_alias.js',
|
||||
'@angularSrc',
|
||||
'src/publishExternalApis.js',
|
||||
'@angularSrcModules',
|
||||
'@angularScenario',
|
||||
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
|
||||
'@angularTest',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'test/matchers.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'test/directive/*.js',
|
||||
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdJqueryExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'src/ngScenario/angular-bootstrap.js',
|
||||
'test/jquery_remove.js'
|
||||
]
|
||||
};
|
||||
|
||||
// Execute only in slim-jim
|
||||
if (typeof JASMINE_ADAPTER !== 'undefined') {
|
||||
// SlimJim config
|
||||
files = [JASMINE, JASMINE_ADAPTER];
|
||||
angularFiles.jstd.forEach(function(pattern) {
|
||||
// replace angular source
|
||||
if (pattern === '@angularSrc') files = files.concat(angularFiles.angularSrc);
|
||||
// ignore jstd and jasmine files
|
||||
else if (!/jstd|jasmine/.test(pattern)) files.push(pattern);
|
||||
// Testacular config
|
||||
var mergedFiles = [];
|
||||
angularFiles.jstd.forEach(function(file) {
|
||||
// replace @ref
|
||||
var match = file.match(/^\@(.*)/);
|
||||
if (match) {
|
||||
var deps = angularFiles[match[1]];
|
||||
if (!deps) {
|
||||
console.log('No dependency:' + file)
|
||||
}
|
||||
mergedFiles = mergedFiles.concat(deps);
|
||||
} else {
|
||||
mergedFiles.push(file);
|
||||
}
|
||||
});
|
||||
|
||||
files = [JASMINE, JASMINE_ADAPTER];
|
||||
|
||||
mergedFiles.forEach(function(file){
|
||||
if (/jstd|jasmine/.test(file)) return;
|
||||
files.push(file);
|
||||
});
|
||||
|
||||
|
||||
exclude = angularFiles.jstdExclude;
|
||||
|
||||
autoWatch = true;
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// TODO(vojta): pre-commit hook for validating messages
|
||||
// TODO(vojta): report errors, currently Q silence everything which really sucks
|
||||
|
||||
var child = require('child_process');
|
||||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
var q = require('qq');
|
||||
|
||||
var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
|
||||
var GIT_TAG_CMD = 'git describe --tags --abbrev=0';
|
||||
|
||||
var HEADER_TPL = '<a name="%s"></a>\n# %s (%s)\n\n';
|
||||
var LINK_ISSUE = '[#%s](https://github.com/angular/angular.js/issues/%s)';
|
||||
var LINK_COMMIT = '[%s](https://github.com/angular/angular.js/commit/%s)';
|
||||
|
||||
var EMPTY_COMPONENT = '$$';
|
||||
var MAX_SUBJECT_LENGTH = 80;
|
||||
|
||||
|
||||
var warn = function() {
|
||||
console.log('WARNING:', util.format.apply(null, arguments));
|
||||
};
|
||||
|
||||
|
||||
var parseRawCommit = function(raw) {
|
||||
if (!raw) return null;
|
||||
|
||||
var lines = raw.split('\n');
|
||||
var msg = {}, match;
|
||||
|
||||
msg.hash = lines.shift();
|
||||
msg.subject = lines.shift();
|
||||
msg.closes = [];
|
||||
msg.breaks = [];
|
||||
|
||||
lines.forEach(function(line) {
|
||||
match = line.match(/Closes\s#(\d+)/);
|
||||
if (match) msg.closes.push(parseInt(match[1]));
|
||||
});
|
||||
|
||||
match = raw.match(/BREAKING CHANGE:([\s\S]*)/);
|
||||
if (match) {
|
||||
console.log('found!!!')
|
||||
msg.breaks.push(match[1]);
|
||||
}
|
||||
|
||||
|
||||
msg.body = lines.join('\n');
|
||||
match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/);
|
||||
|
||||
if (!match || !match[1] || !match[3]) {
|
||||
warn('Incorrect message: %s %s', msg.hash, msg.subject);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (match[3].length > MAX_SUBJECT_LENGTH) {
|
||||
warn('Too long subject: %s %s', msg.hash, msg.subject);
|
||||
match[3] = match[3].substr(0, MAX_SUBJECT_LENGTH);
|
||||
}
|
||||
|
||||
msg.type = match[1];
|
||||
msg.component = match[2];
|
||||
msg.subject = match[3];
|
||||
|
||||
return msg;
|
||||
};
|
||||
|
||||
|
||||
var linkToIssue = function(issue) {
|
||||
return util.format(LINK_ISSUE, issue, issue);
|
||||
};
|
||||
|
||||
|
||||
var linkToCommit = function(hash) {
|
||||
return util.format(LINK_COMMIT, hash.substr(0, 8), hash);
|
||||
};
|
||||
|
||||
|
||||
var currentDate = function() {
|
||||
var now = new Date();
|
||||
var pad = function(i) {
|
||||
return ('0' + i).substr(-2);
|
||||
};
|
||||
|
||||
return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
|
||||
};
|
||||
|
||||
|
||||
var printSection = function(stream, title, section) {
|
||||
var components = Object.getOwnPropertyNames(section).sort();
|
||||
|
||||
if (!components.length) return;
|
||||
|
||||
stream.write(util.format('\n## %s\n\n', title));
|
||||
|
||||
components.forEach(function(name) {
|
||||
var prefix = '-';
|
||||
var nested = section[name].length > 1;
|
||||
|
||||
if (name !== EMPTY_COMPONENT) {
|
||||
if (nested) {
|
||||
stream.write(util.format('- **%s:**\n', name));
|
||||
prefix = ' -';
|
||||
} else {
|
||||
prefix = util.format('- **%s:**', name);
|
||||
}
|
||||
}
|
||||
|
||||
section[name].forEach(function(commit) {
|
||||
stream.write(util.format('%s %s (%s', prefix, commit.subject, linkToCommit(commit.hash)));
|
||||
if (commit.closes.length) {
|
||||
stream.write(', closes ' + commit.closes.map(linkToIssue).join(', '));
|
||||
}
|
||||
stream.write(')\n');
|
||||
});
|
||||
});
|
||||
|
||||
stream.write('\n');
|
||||
};
|
||||
|
||||
|
||||
var readGitLog = function(grep, from) {
|
||||
var deffered = q.defer();
|
||||
|
||||
// TODO(vojta): if it's slow, use spawn and stream it instead
|
||||
child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) {
|
||||
var commits = [];
|
||||
|
||||
stdout.split('\n==END==\n').forEach(function(rawCommit) {
|
||||
var commit = parseRawCommit(rawCommit);
|
||||
if (commit) commits.push(commit);
|
||||
});
|
||||
|
||||
deffered.resolve(commits);
|
||||
});
|
||||
|
||||
return deffered.promise;
|
||||
};
|
||||
|
||||
|
||||
var writeChangelog = function(stream, commits, version) {
|
||||
var sections = {
|
||||
fix: {},
|
||||
feat: {},
|
||||
breaks: {}
|
||||
};
|
||||
|
||||
sections.breaks[EMPTY_COMPONENT] = [];
|
||||
|
||||
commits.forEach(function(commit) {
|
||||
var section = sections[commit.type];
|
||||
var component = commit.component || EMPTY_COMPONENT;
|
||||
|
||||
if (section) {
|
||||
section[component] = section[component] || [];
|
||||
section[component].push(commit);
|
||||
}
|
||||
|
||||
commit.breaks.forEach(function(breakMsg) {
|
||||
sections.breaks[EMPTY_COMPONENT].push({
|
||||
subject: breakMsg,
|
||||
hash: commit.hash,
|
||||
closes: []
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
stream.write(util.format(HEADER_TPL, version, version, currentDate()));
|
||||
printSection(stream, 'Bug Fixes', sections.fix);
|
||||
printSection(stream, 'Features', sections.feat);
|
||||
printSection(stream, 'Breaking Changes', sections.breaks);
|
||||
}
|
||||
|
||||
|
||||
var getPreviousTag = function() {
|
||||
var deffered = q.defer();
|
||||
child.exec(GIT_TAG_CMD, function(code, stdout, stderr) {
|
||||
if (code) deffered.reject('Cannot get the previous tag.');
|
||||
else deffered.resolve(stdout.replace('\n', ''));
|
||||
});
|
||||
return deffered.promise;
|
||||
};
|
||||
|
||||
|
||||
var generate = function(version, file) {
|
||||
getPreviousTag().then(function(tag) {
|
||||
console.log('Reading git log since', tag);
|
||||
readGitLog('^fix|^feat|Breaks', tag).then(function(commits) {
|
||||
console.log('Parsed', commits.length, 'commits');
|
||||
console.log('Generating changelog to', file || 'stdout', '(', version, ')');
|
||||
writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// publish for testing
|
||||
exports.parseRawCommit = parseRawCommit;
|
||||
|
||||
// hacky start if not run by jasmine :-D
|
||||
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
||||
generate(process.argv[2], process.argv[3]);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
describe('changelog.js', function() {
|
||||
var ch = require('./changelog');
|
||||
|
||||
describe('parseRawCommit', function() {
|
||||
it('should parse raw commit', function() {
|
||||
var msg = ch.parseRawCommit(
|
||||
'9b1aff905b638aa274a5fc8f88662df446d374bd\n' +
|
||||
'feat(scope): broadcast $destroy event on scope destruction\n' +
|
||||
'perf testing shows that in chrome this change adds 5-15% overhead\n' +
|
||||
'when destroying 10k nested scopes where each scope has a $destroy listener\n');
|
||||
|
||||
expect(msg.type).toBe('feat');
|
||||
expect(msg.hash).toBe('9b1aff905b638aa274a5fc8f88662df446d374bd');
|
||||
expect(msg.subject).toBe('broadcast $destroy event on scope destruction');
|
||||
expect(msg.body).toBe('perf testing shows that in chrome this change adds 5-15% overhead\n' +
|
||||
'when destroying 10k nested scopes where each scope has a $destroy listener\n')
|
||||
expect(msg.component).toBe('scope');
|
||||
});
|
||||
|
||||
|
||||
it('should parse closed issues', function() {
|
||||
var msg = ch.parseRawCommit(
|
||||
'13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
|
||||
'feat(ng-list): Allow custom separator\n' +
|
||||
'bla bla bla\n\n' +
|
||||
'Closes #123\nCloses #25\n');
|
||||
|
||||
expect(msg.closes).toEqual([123, 25]);
|
||||
});
|
||||
|
||||
|
||||
it('should parse breaking changes', function() {
|
||||
var msg = ch.parseRawCommit(
|
||||
'13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
|
||||
'feat(ng-list): Allow custom separator\n' +
|
||||
'bla bla bla\n\n' +
|
||||
'Breaks first breaking change\nsomething else\n' +
|
||||
'Breaks another breaking change\n');
|
||||
|
||||
expect(msg.breaks).toEqual(['first breaking change', 'another breaking change']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
<a name="v1.0.0rc3"></a>
|
||||
# v1.0.0rc3 (2012-03-27)
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **$compile:**
|
||||
- create new (isolate) scopes for directives on root elements ([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787), closes [#817](https://github.com/angular/angular.js/issues/817))
|
||||
- don't touch static element attributes ([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
|
||||
- Merge interpolated css class when replacing an element ([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
|
||||
- **$http:**
|
||||
- don't send Content-Type header when no data ([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb), closes [#749](https://github.com/angular/angular.js/issues/749))
|
||||
- **$log:**
|
||||
- avoid console.log.apply calls in IE ([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca), closes [#805](https://github.com/angular/angular.js/issues/805))
|
||||
- **$resource:**
|
||||
- support escaping of ':' in resource url ([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
|
||||
- **compiler:**
|
||||
- allow transclusion of root elements ([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
|
||||
- **e2e runner:**
|
||||
- fix typo that caused errors on IE8 ([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b), closes [#806](https://github.com/angular/angular.js/issues/806))
|
||||
- **forEach:**
|
||||
- should ignore prototypically inherited properties ([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61), closes [#813](https://github.com/angular/angular.js/issues/813))
|
||||
- **forms:**
|
||||
- Remove double registering of form ([1faafa31](https://github.com/angular/angular.js/commit/1faafa31582c4e9413f48dc7d12f5b681f9fe9fd))
|
||||
- Set ng-valid/ng-invalid correctly ([08bfea18](https://github.com/angular/angular.js/commit/08bfea183a850b29da270eac47f80b598cbe600f))
|
||||
- **init:**
|
||||
- use jQuery#ready for init if available ([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d), closes [#818](https://github.com/angular/angular.js/issues/818))
|
||||
- **json:**
|
||||
- added support for iso8061 timezone ([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
|
||||
- **matchers.toHaveClass:**
|
||||
- Correct reference to angular.mock.dump ([f701ce08](https://github.com/angular/angular.js/commit/f701ce08f9d63be05fc3b92f57ad473e1e749b2d))
|
||||
- **ng-switch:**
|
||||
- properly destroy child scopes ([2315d9b3](https://github.com/angular/angular.js/commit/2315d9b3610994b36c44e4a97fb1427d59471ce8))
|
||||
- **ngDocSpec:**
|
||||
- fix broken tests ([53b6f522](https://github.com/angular/angular.js/commit/53b6f522a56eea314cbd084816e08f24b2c7879f))
|
||||
- **ngForm:**
|
||||
- alias name||ngForm ([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
|
||||
- **ngRepeat:**
|
||||
- correct variable reference in error message ([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
|
||||
- **ngView:**
|
||||
- controller not published ([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
|
||||
- **q:**
|
||||
- resolve all of nothing to nothing ([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
|
||||
- **select:**
|
||||
- multiselect failes to update view on selection insert ([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **$compile:**
|
||||
- do not interpolate boolean attributes, rather evaluate them ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
|
||||
- **$controller:**
|
||||
- support controller registration via $controllerProvider ([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
|
||||
- **$route:**
|
||||
- when matching consider trailing slash as optional ([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6), closes [#784](https://github.com/angular/angular.js/issues/784))
|
||||
- **assertArgFn:**
|
||||
- should support array annotated fns ([4b8d9260](https://github.com/angular/angular.js/commit/4b8d926062eb4d4483555bdbdec4656f585ab40b))
|
||||
- **http:**
|
||||
- added params parameter ([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
|
||||
- **injector:**
|
||||
- infer _foo_ as foo ([f13dd339](https://github.com/angular/angular.js/commit/f13dd3393dfb7a33565c9360342c193bc0bddcb6))
|
||||
- **input.radio:**
|
||||
- Allow value attribute to be interpolated ([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
|
||||
- **jqLite:**
|
||||
- make injector() and scope() work with the document object ([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
|
||||
- add .controller() method ([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
|
||||
- **ngValue:**
|
||||
- allow radio inputs to have non string values ([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b), closes [#816](https://github.com/angular/angular.js/issues/816))
|
||||
- **scope:**
|
||||
- broadcast $destroy event on scope destruction ([9b1aff90](https://github.com/angular/angular.js/commit/9b1aff905b638aa274a5fc8f88662df446d374bd))
|
||||
- **scope.$eval:**
|
||||
- Allow passing locals to the expression ([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- boolean attrs are evaluated rather than interpolated ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
|
||||
- ng-bind-attr directive removed ([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
|
||||
- any app that depends on this service and its fallback to Modernizr, please ([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
rake compile
|
||||
gzip -c < build/angular.min.js > build/angular.min.js.gzip
|
||||
ls -l build/angular.min.*
|
||||
@@ -38,7 +38,7 @@ the following example.
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl1">
|
||||
Hello <input ng-model='name' ng-model-instant> <hr/>
|
||||
Hello <input ng-model='name'> <hr/>
|
||||
<span ng:bind="name"> <span ng:bind="name"></span> <br/>
|
||||
<span ng_bind="name"> <span ng_bind="name"></span> <br/>
|
||||
<span ng-bind="name"> <span ng-bind="name"></span> <br/>
|
||||
@@ -84,7 +84,7 @@ Compilation of HTML happens in three phases:
|
||||
collection of all of the linking functions returned from the individual directive compile
|
||||
functions.
|
||||
|
||||
3. Link the template with scope by calling the liking function returned from the previous step.
|
||||
3. Link the template with scope by calling the linking function returned from the previous step.
|
||||
This in turn will call the linking function of the individual directives allowing them to
|
||||
register any listeners on the elements and set up any {@link
|
||||
angular.module.ng.$rootScope.Scope#$watch watches} with the {@link
|
||||
@@ -126,8 +126,8 @@ a change in DOM structure such as in repeaters.
|
||||
|
||||
When the above example is compiled, the compiler visits every node and looks for directives. The
|
||||
`{{user}}` is an example of {@link angular.module.ng.$interpolate interpolation} directive. {@link
|
||||
angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} is another directive. But {@link
|
||||
angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} has a dilemma. It needs to be
|
||||
angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} is another directive. But {@link
|
||||
angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} has a dilemma. It needs to be
|
||||
able to quickly stamp out new `li`s for every `action` in `user.actions`. This means that it needs
|
||||
to save a clean copy of the `li` element for cloning purposes and as new `action`s are inserted,
|
||||
the template `li` element needs to be cloned and inserted into `ul`. But cloning the `li` element
|
||||
@@ -143,12 +143,12 @@ the directives are identified and sorted by priority, and a linking phase where
|
||||
links a specific instance of the {@link angular.module.ng.$rootScope.Scope scope} and the specific
|
||||
instance of an `li` is performed.
|
||||
|
||||
{@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} works by preventing the
|
||||
{@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} works by preventing the
|
||||
compilation process form descending into `li` element. Instead the {@link
|
||||
angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} directive compiles `li`
|
||||
angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} directive compiles `li`
|
||||
seperatly. The result of of the `li` element compilation is a linking function which contains all
|
||||
of the directives contained in the `li` element ready to be attached to a specific clone of `li`
|
||||
element. At runtime the {@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat}
|
||||
element. At runtime the {@link angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}
|
||||
watches the expression and as items are added to the array it clones the `li` element, creates a
|
||||
new {@link angular.module.ng.$rootScope.Scope scope} for the cloned `li` element and calls the
|
||||
link function on the cloned `li`.
|
||||
@@ -407,7 +407,7 @@ compiler}. The attributes are:
|
||||
migrates all of the attributes / classes from the old element to the new one. See Creating
|
||||
Widgets section below for more information.
|
||||
|
||||
* `templateURL` - Same as `template` but the template is loaded from the specified URL. Because
|
||||
* `templateUrl` - Same as `template` but the template is loaded from the specified URL. Because
|
||||
the template loading is asynchronous the compilation/linking is suspended until the template
|
||||
is loaded.
|
||||
|
||||
@@ -415,8 +415,8 @@ compiler}. The attributes are:
|
||||
append the template to the element.
|
||||
|
||||
* `transclude` - compile the content of the element and make it available to the directive.
|
||||
Typically used with {@link api/angular.module.ng.$compileProvider.directive.ng-transclude
|
||||
ng-transclude}. The advantage of transclusion is that the linking function receives a
|
||||
Typically used with {@link api/angular.module.ng.$compileProvider.directive.ngTransclude
|
||||
ngTransclude}. The advantage of transclusion is that the linking function receives a
|
||||
transclusion function which is pre-bound to the correct scope. In a typical setup the widget
|
||||
creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
|
||||
scope. This makes it possible for the widget to have private state, and the transclusion to
|
||||
@@ -440,8 +440,8 @@ compiler}. The attributes are:
|
||||
Compile function deals with transforming the template DOM. Since most directives do not do
|
||||
template transformation, it is not used often. Examples which require compile functions are
|
||||
directives which transform template DOM such as {@link
|
||||
angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} or load the contents
|
||||
asynchronously such as {@link angular.module.ng.$compileProvider.directive.ng-view ng-view}. The
|
||||
angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} or load the contents
|
||||
asynchronously such as {@link angular.module.ng.$compileProvider.directive.ngView ngView}. The
|
||||
compile functions takes the following arguments.
|
||||
|
||||
* `tElement` - template element - The element where the directive has been declared. It is
|
||||
@@ -584,10 +584,10 @@ expects as follows:
|
||||
|
||||
<pre>
|
||||
scope: {
|
||||
title: 'bind', // set up title to accept data-binding
|
||||
onOk: 'exp', // create a delegate onOk function
|
||||
onCancel: 'exp', // create a delegate onCancel function
|
||||
show: 'prop' // create a getter/setter function for visibility.
|
||||
title: 'bind', // set up title to accept data-binding
|
||||
onOk: 'expression', // create a delegate onOk function
|
||||
onCancel: 'expression', // create a delegate onCancel function
|
||||
show: 'accessor' // create a getter/setter function for visibility.
|
||||
}
|
||||
</pre>
|
||||
|
||||
@@ -618,10 +618,10 @@ Therefore the final directive definition looks something like this:
|
||||
<pre>
|
||||
transclude: true,
|
||||
scope: {
|
||||
title: 'bind', // set up title to accept data-binding
|
||||
onOk: 'exp', // create a delegate onOk function
|
||||
onCancel: 'exp', // create a delegate onCancel function
|
||||
show: 'prop' // create a getter/setter function for visibility.
|
||||
title: 'bind', // set up title to accept data-binding
|
||||
onOk: 'expression', // create a delegate onOk function
|
||||
onCancel: 'expression', // create a delegate onCancel function
|
||||
show: 'accessor' // create a getter/setter function for visibility.
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -85,8 +85,8 @@ detection, and preventing invalid form submission.
|
||||
<input type="text" ng-model="contact.value" required/>
|
||||
[ <a href="" ng-click="removeContact(contact)">X</a> ]
|
||||
</div>
|
||||
<button ng-click="cancel()" ng-disabled="{{isCancelDisabled()}}">Cancel</button>
|
||||
<button ng-click="save()" ng-disabled="{{isSaveDisabled()}}">Save</button>
|
||||
<button ng-click="cancel()" ng-disabled="isCancelDisabled()">Cancel</button>
|
||||
<button ng-click="save()" ng-disabled="isSaveDisabled()">Save</button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Deep linking allows you to encode the state of the application in the URL so that it can be
|
||||
bookmarked and the application can be restored from the URL to the same state.
|
||||
|
||||
While <angular/> does not force you to deal with bookmarks in any particular way, it has services
|
||||
While angular does not force you to deal with bookmarks in any particular way, it has services
|
||||
which make the common case described here very easy to implement.
|
||||
|
||||
# Assumptions
|
||||
@@ -33,13 +33,13 @@ In this example we have a simple app which consist of two screens:
|
||||
|
||||
The two partials are defined in the following URLs:
|
||||
|
||||
* <a href="./examples/settings.html" ng-ext-link>./examples/settings.html</a>
|
||||
* <a href="./examples/welcome.html" ng-ext-link>./examples/welcome.html</a>
|
||||
* <a href="examples/settings.html" target="_self">./examples/settings.html</a>
|
||||
* <a href="examples/welcome.html" target="_self">./examples/welcome.html</a>
|
||||
|
||||
<doc:example module="deepLinking">
|
||||
<doc:source jsfiddle="false">
|
||||
<script>
|
||||
angular.module('deepLinking', [])
|
||||
angular.module('deepLinking', ['ngSanitize'])
|
||||
.config(function($routeProvider) {
|
||||
$routeProvider.when("/welcome", {template:'./examples/welcome.html', controller:WelcomeCntl});
|
||||
$routeProvider.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
|
||||
@@ -106,7 +106,7 @@ The two partials are defined in the following URLs:
|
||||
routes.
|
||||
* The {@link api/angular.module.ng.$route $route} service then watches the URL and instantiates the
|
||||
appropriate controller when the URL changes.
|
||||
* The {@link api/angular.module.ng.$compileProvider.directive.ng-view ng-view} widget loads the
|
||||
* The {@link api/angular.module.ng.$compileProvider.directive.ngView ngView} widget loads the
|
||||
view when the URL changes. It also sets the view scope to the newly instantiated controller.
|
||||
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
|
||||
the URL is changed programatically or by the user.
|
||||
|
||||
@@ -20,7 +20,7 @@ allow a user to enter data.
|
||||
$scope.zip = /^\d\d\d\d\d$/;
|
||||
|
||||
$scope.addContact = function() {
|
||||
$scope.user.contacts.push({type:'', value:''});
|
||||
$scope.user.contacts.push({type:'email', value:''});
|
||||
};
|
||||
|
||||
$scope.removeContact = function(contact) {
|
||||
@@ -34,16 +34,16 @@ allow a user to enter data.
|
||||
</script>
|
||||
<div ng-controller="FormController" class="example">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" ng-model="user.name" required/> <br/><br/>
|
||||
<label>Name:</label><br>
|
||||
<input type="text" ng-model="user.name" required/> <br><br>
|
||||
|
||||
<label>Address:</label><br/>
|
||||
<input type="text" ng-model="user.address.line1" size="33" required> <br/>
|
||||
<label>Address:</label><br>
|
||||
<input type="text" ng-model="user.address.line1" size="33" required> <br>
|
||||
<input type="text" ng-model="user.address.city" size="12" required>,
|
||||
<input type="text" ng-model="user.address.state" size="2"
|
||||
ng-pattern="state" required>
|
||||
<input type="text" ng-model="user.address.state"
|
||||
ng-pattern="state" size="2" required>
|
||||
<input type="text" ng-model="user.address.zip" size="5"
|
||||
ng-pattern="zip" required><br/><br/>
|
||||
ng-pattern="zip" required><br><br>
|
||||
|
||||
<label>Phone:</label>
|
||||
[ <a href="" ng-click="addContact()">add</a> ]
|
||||
@@ -54,12 +54,12 @@ allow a user to enter data.
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" ng-model="contact.value" required/>
|
||||
<input type="text" ng-model="contact.value" required>
|
||||
[ <a href="" ng-click="removeContact(contact)">X</a> ]
|
||||
</div>
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>user={{user}}</pre>
|
||||
<pre>user={{user | json}}</pre>
|
||||
</div>
|
||||
|
||||
</doc:source>
|
||||
@@ -102,7 +102,7 @@ allow a user to enter data.
|
||||
|
||||
# Things to notice
|
||||
|
||||
* The user data model is initialized {@link api/angular.module.ng.$compileProvider.directive.ng-controller controller} and is
|
||||
* The user data model is initialized {@link api/angular.module.ng.$compileProvider.directive.ngController controller} and is
|
||||
available in the {@link api/angular.module.ng.$rootScope.Scope scope} with the initial data.
|
||||
* For debugging purposes we have included a debug view of the model to better understand what
|
||||
is going on.
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
@name Developer Guide: Initializing Angular: Automatic Initialization
|
||||
@description
|
||||
|
||||
Angular initializes automatically when you load the angular script into your page that contains an element
|
||||
with `ng-app` directive:
|
||||
For Angular to manage the DOM for your application, it needs to compile some or all of an HTML page. Angular does this initialization automatically when you load the angular.js script into your page and insert an `ngApp` directive (attribute) into one of the page's elements. For example, we can tell Angular to initialize the entire document:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
@@ -17,30 +16,36 @@ with `ng-app` directive:
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
From a high-level view, this is what happens during angular's automatic initialization process:
|
||||
You can also tell Angular to manage only a portion of a page. You would want to do this if you are using some other framework to manage other parts of the page. You do this by placing the `ngApp` directive on one or more container elements in the document. For example:
|
||||
|
||||
1. The browser loads the page, and then runs the angular script. Angular waits for the
|
||||
`DOMContentLoaded` (or 'Load') event to attempt to bootstrap.
|
||||
<pre>
|
||||
<div ng-app>
|
||||
I can add: {{ 1+2 }}
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
2. Angular looks for the `ng-app` directive. If found it then proceeds to compile the DOM element and its children.
|
||||
Optionally the `ng-app` may specify a {@link api/angular.module module} to load before the compilation. For details on
|
||||
how the compiler works, see {@link dev_guide.compiler Angular HTML Compiler}.
|
||||
You can also ask `ngApp` to load additional {@link api/angular.module modules} containing services, directives or filers that you'll use on the page.
|
||||
|
||||
<pre>
|
||||
<div ng-app="AwesomeModule">
|
||||
...
|
||||
</div>
|
||||
</pre
|
||||
|
||||
|
||||
## Initialization Options
|
||||
From a high-level, here's what Angular does during the initialization process:
|
||||
|
||||
The reason why `ng-app` exists is because angular should not assume that the entire HTML
|
||||
document should be processed just because the `angular.js` script is included. In order to compile
|
||||
only a part of the document set the `ng-app` on the root element of this portion.
|
||||
1. The browser loads the page, and then runs the Angular script. Angular then waits for the
|
||||
`DOMContentLoaded` (or 'Load') event to attempt to initialize.
|
||||
|
||||
## Global Angular Object
|
||||
2. Angular looks for the `ngApp` directive. If found it compilies the DOM element containing `ngApp` and its children.
|
||||
|
||||
The angular script creates a single global variable `angular` in the global namespace. All angular
|
||||
APIs are bound to fields of this global object.
|
||||
3. Angular creates a global variable `angular` and binds all Angular APIs to this object's fields.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.compiler Angular HTML Compiler}
|
||||
* {@link dev_guide.bootstrap Initializing Angular}
|
||||
* {@link dev_guide.bootstrap.manual_bootstrap Manual Initialization}
|
||||
|
||||
|
||||
@@ -2,13 +2,26 @@
|
||||
@name Developer Guide: Initializing Angular: Manual Initialization
|
||||
@description
|
||||
|
||||
Letting angular handle the initialization process (bootstrapping) is a handy way to start using
|
||||
angular, but advanced users who want more control over the initialization process can choose to use
|
||||
the manual bootstrapping method instead.
|
||||
In the vast majority of cases you'll want to let Angular handle initialization automatically.
|
||||
If, however, you need to delay Angular from managing the page right after the DOMContentLoaded
|
||||
event fires, you'll need to control this initialization manually.
|
||||
|
||||
The best way to get started with manual bootstrapping is to look at the what happens when you use
|
||||
{@link api/angular.module.ng.$compileProvider.directive.ng-app ng-app}, by showing each step of the process
|
||||
explicitly.
|
||||
To initialize Angular -- after you've done your own special-purpose initialization -- just call
|
||||
the {@link api/angular.bootstrap bootstrap()} function with the HTML container node that you want
|
||||
Angular to manage. In automatic initialization you'd do this by adding the `ngApp` attribute to
|
||||
the same node. Now, you won't use `ngApp` anywhere in your document.
|
||||
|
||||
To show the contrast of manual vs. automatic initialization, this automatic method:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html ng-app>
|
||||
<head>
|
||||
<script src="http://code.angularjs.org/angular.js"></script>
|
||||
...
|
||||
</pre
|
||||
|
||||
is the same as this manual method:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
@@ -21,19 +34,9 @@ explicitly.
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Hello {{'World'}}!
|
||||
</body>
|
||||
</html>
|
||||
...
|
||||
</pre>
|
||||
|
||||
This is the sequence that your code should follow if you bootstrap angular on your own:
|
||||
|
||||
1. After the page is loaded, find the root of the HTML template, which is typically the root of
|
||||
the document.
|
||||
2. Call {@link api/angular.bootstrap} to {@link dev_guide.compiler compile} the template into
|
||||
an executable, bi-directionally bound application.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
@name Developer Guide: Initializing Angular
|
||||
@description
|
||||
|
||||
Initializing angular consists of loading the `angular.js` script in your page, and specifying how
|
||||
angular should process and manage the page. To initialize angular you do the following:
|
||||
Initializing Angular consists of loading the `angular.js` script in your page, and specifying how
|
||||
Angular should process and manage the page. To initialize Angular you do the following:
|
||||
|
||||
* Specify the angular namespace in the `<html>` page
|
||||
* Choose which flavor of angular script to load (debug or production)
|
||||
* Specify whether or not angular should process and manage the page automatically (`ng-app`)
|
||||
* Specify the Angular namespace in the `<html>` page
|
||||
* Choose which flavor of Angular script to load (debug or production)
|
||||
* Specify whether or not Angular should process and manage the page automatically (`ngApp`)
|
||||
|
||||
The simplest way to initialize angular is to load the angular script and tell angular to compile
|
||||
The simplest way to initialize Angular is to load the Angular script and tell Angular to compile
|
||||
and manage the whole page. You do this as follows:
|
||||
|
||||
<pre>
|
||||
@@ -29,8 +29,8 @@ and manage the whole page. You do this as follows:
|
||||
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
|
||||
You need to add the angular namespace declaration if you use `ng:something` style of declaring
|
||||
angular directives and you write your templates as XHTML. Or when you are targeting Internet
|
||||
You need to add the Angular namespace declaration if you use `ng:something` style of declaring
|
||||
Angular directives and you write your templates as XHTML. Or when you are targeting Internet
|
||||
Explorer older than version 9 (because older versions of IE do not render namespace
|
||||
properly for either HTML or XHTML). For more info please read {@link ie Internet Explorer
|
||||
Compatibility} doc.
|
||||
@@ -39,7 +39,7 @@ Compatibility} doc.
|
||||
## Creating Your Own Namespaces
|
||||
|
||||
When you are ready to define your own {@link api/angular.module.ng.$compileProvider.directive
|
||||
directive}, you may chose to create your own namespace in addition to specifying the angular
|
||||
directive}, you may chose to create your own namespace in addition to specifying the Angular
|
||||
namespace. You use your own namespace to form the fully qualified name for directives that you
|
||||
create.
|
||||
|
||||
@@ -52,7 +52,7 @@ it to your unique domain:
|
||||
|
||||
## Loading the Angular Bootstrap Script
|
||||
|
||||
The angular bootstrap script comes in two flavors; a debug script, and a production script:
|
||||
The Angular bootstrap script comes in two flavors; a debug script, and a production script:
|
||||
|
||||
* angular-[version].js - This is a human-readable file, suitable for development and debugging.
|
||||
* angular-[version].min.js - This is a compressed and obfuscated file, suitable for use in
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
@name Developer Guide: Angular HTML Compiler
|
||||
@description
|
||||
|
||||
The core of angular is its HTML compiler. The compiler processes angular directives allowing them
|
||||
to transform a static HTML page into a dynamic web application.
|
||||
The core of Angular is its HTML compiler. The compiler processes Angular
|
||||
{@link api/angular.module.ng.$compileProvider.directive directives} allowing them to transform a
|
||||
static HTML page into a dynamic web application.
|
||||
|
||||
The default HTML transformations that the angular compiler provides are useful for building generic
|
||||
The default HTML transformations that the Angular compiler provides are useful for building generic
|
||||
apps, but you can also extend the compiler to create a domain-specific language for building
|
||||
specific types of web applications.
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ In the illustration above, the dependency injection sequence proceeds as follows
|
||||
|
||||
1. Module "phonecat" is created and all the service providers are registered with this module.
|
||||
(the "ng" module is created by Angular behind the scenes as well)
|
||||
2. `ng-app` triggers bootstrap sequence on given element, during which angular creates injector,
|
||||
2. `ngApp` triggers bootstrap sequence on given element, during which angular creates injector,
|
||||
loads "phonecat" and "ng" modules and compiles the template.
|
||||
3. The `ng-controller` directive implicitly creates a new child scope and instantiates
|
||||
3. The `ngController` directive implicitly creates a new child scope and instantiates
|
||||
`PhoneListCtrl` controller.
|
||||
4. Injector identifies the `$http` service as `PhoneListCtrl` controller's only dependency.
|
||||
5. Injector checks its instances cache whether the `$http` service has already been instantiated.
|
||||
|
||||
@@ -6,18 +6,17 @@ The most common place to use dependency injection in angular applications is in
|
||||
dev_guide.mvc.understanding_controller controllers}. Here is a simple example:
|
||||
|
||||
<pre>
|
||||
function MyController($route){
|
||||
// configure the route service
|
||||
$route.when(...);
|
||||
function MyController($location){
|
||||
// do stuff with the $location service
|
||||
}
|
||||
MyController.$inject = ['$route'];
|
||||
MyController.$inject = ['$location'];
|
||||
</pre>
|
||||
|
||||
In this example, the `MyController` constructor function takes one argument, the {@link
|
||||
api/angular.module.ng.$route $route} service. Angular is then responsible for supplying the instance
|
||||
of `$route` to the controller when the constructor is instantiated. There are two ways to cause
|
||||
controller instantiation – by configuring routes with the `$route` service, or by referencing the
|
||||
controller from the HTML template, as follows:
|
||||
api/angular.module.ng.$location $location} service. Angular is then responsible for supplying the
|
||||
instance of `$location` to the controller when the constructor is instantiated. There are two ways
|
||||
to cause controller instantiation – by configuring routes with the `$location` service, or by
|
||||
referencing the controller from the HTML template, as follows:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
@@ -35,7 +34,7 @@ we have to supply this information to angular in the form of an additional prope
|
||||
controller constructor function called `$inject`. Think of it as annotations for JavaScript.
|
||||
|
||||
<pre>
|
||||
MyController.$inject = ['$route'];
|
||||
MyController.$inject = ['$location'];
|
||||
</pre>
|
||||
|
||||
The information in `$inject` is then used by the {@link api/angular.injector injector} to call the
|
||||
|
||||
@@ -45,7 +45,7 @@ the only button on the page, and then it verifies that there are 10 items listed
|
||||
The API section below lists the available commands and expectations for the Runner.
|
||||
|
||||
# API
|
||||
Source: {@link https://github.com/angular/angular.js/blob/master/src/scenario/dsl.js}
|
||||
Source: {@link https://github.com/angular/angular.js/blob/master/src/ngScenario/dsl.js}
|
||||
|
||||
## pause()
|
||||
Pauses the execution of the tests until you call `resume()` in the console (or click the resume
|
||||
|
||||
@@ -12,15 +12,15 @@ Server-side validation is still necessary for a secure application.
|
||||
|
||||
|
||||
# Simple form
|
||||
The key directive in understanding two-way data-binding is {@link api/angular.module.ng.$compileProvider.directive.ng-model ng-model}.
|
||||
The `ng-model` provides the two-way data-binding by synchronizing the model to the view, as well as view to the model.
|
||||
In addition it provides {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController API} for other directives to augment its behavior.
|
||||
The key directive in understanding two-way data-binding is {@link api/angular.module.ng.$compileProvider.directive.ngModel ngModel}.
|
||||
The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view, as well as view to the model.
|
||||
In addition it provides {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController API} for other directives to augment its behavior.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng-controller="Controller">
|
||||
<form novalidate class="simple-form">
|
||||
Name: <input type="text" ng-model="user.name" ng-model-instant /><br />
|
||||
Name: <input type="text" ng-model="user.name" /><br />
|
||||
E-mail: <input type="email" ng-model="user.email" /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
@@ -50,17 +50,13 @@ In addition it provides {@link api/angular.module.ng.$compileProvider.directive.
|
||||
</doc:example>
|
||||
|
||||
|
||||
Note that:
|
||||
|
||||
* the {@link api/angular.module.ng.$compileProvider.directive.ng-model-instant ng-model-instant} causes the `user.name` to be updated immediately.
|
||||
|
||||
* `novalidate` is used to disable browser's native form validation.
|
||||
Note that `novalidate` is used to disable browser's native form validation.
|
||||
|
||||
|
||||
|
||||
# Using CSS classes
|
||||
|
||||
To allow styling of form as well as controls, `ng-model` add these CSS classes:
|
||||
To allow styling of form as well as controls, `ngModel` add these CSS classes:
|
||||
|
||||
- `ng-valid`
|
||||
- `ng-invalid`
|
||||
@@ -76,7 +72,7 @@ This ensures that the user is not distracted with an error until after interacti
|
||||
<div ng-controller="Controller">
|
||||
<form novalidate class="css-form">
|
||||
Name:
|
||||
<input type="text" ng-model="user.name" ng-model-instant required /><br />
|
||||
<input type="text" ng-model="user.name" required /><br />
|
||||
E-mail: <input type="email" ng-model="user.email" required /><br />
|
||||
Gender: <input type="radio" ng-model="user.gender" value="male" />male
|
||||
<input type="radio" ng-model="user.gender" value="female" />female<br />
|
||||
@@ -119,7 +115,7 @@ This ensures that the user is not distracted with an error until after interacti
|
||||
|
||||
A form is in instance of {@link api/angular.module.ng.$compileProvider.directive.form.FormController FormController}.
|
||||
The form instance can optionally be published into the scope using the `name` attribute.
|
||||
Similarly control is an instance of {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController NgModelController}.
|
||||
Similarly control is an instance of {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController NgModelController}.
|
||||
The control instance can similarly be published into the form instance using the `name` attribute.
|
||||
This implies that the internal state of both the form and the control is available for binding in the view using the standard binding primitives.
|
||||
|
||||
@@ -147,12 +143,12 @@ This allows us to extend the above example with these features:
|
||||
|
||||
<input type="checkbox" ng-model="user.agree" name="userAgree" required />
|
||||
I agree: <input ng-show="user.agree" type="text" ng-model="user.agreeSign"
|
||||
ng-model-instant required /><br />
|
||||
required /><br />
|
||||
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
|
||||
|
||||
<button ng-click="reset()" disabled="{{isUnchanged(user)}}">RESET</button>
|
||||
<button ng-click="reset()" ng-disabled="isUnchanged(user)">RESET</button>
|
||||
<button ng-click="update(user)"
|
||||
disabled="{{form.$invalid || isUnchanged(user)}}">SAVE</button>
|
||||
ng-disabled="form.$invalid || isUnchanged(user)">SAVE</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -185,16 +181,16 @@ This allows us to extend the above example with these features:
|
||||
Angular provides basic implementation for most common html5 {@link api/angular.module.ng.$compileProvider.directive.input input}
|
||||
types: ({@link api/angular.module.ng.$compileProvider.directive.input.text text}, {@link api/angular.module.ng.$compileProvider.directive.input.number number}, {@link api/angular.module.ng.$compileProvider.directive.input.url url}, {@link api/angular.module.ng.$compileProvider.directive.input.email email}, {@link api/angular.module.ng.$compileProvider.directive.input.radio radio}, {@link api/angular.module.ng.$compileProvider.directive.input.checkbox checkbox}), as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`, `min`, `max`).
|
||||
|
||||
Defining your own validator can be done by defining your own directive which adds a custom validation function to the `ng-model` {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController controller}.
|
||||
Defining your own validator can be done by defining your own directive which adds a custom validation function to the `ngModel` {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController controller}.
|
||||
To get a hold of the controller the directive specifies a dependency as shown in the example below.
|
||||
The validation can occur in two places:
|
||||
|
||||
* **Model to View update** -
|
||||
Whenever the bound model changes, all functions in {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$formatters NgModelController#$formatters} array are pipe-lined, so that each of these functions has an opportunity to format the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$setValidity NgModelController#$setValidity}.
|
||||
Whenever the bound model changes, all functions in {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$formatters NgModelController#$formatters} array are pipe-lined, so that each of these functions has an opportunity to format the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
|
||||
|
||||
* **View to Model update** -
|
||||
In a similar way, whenever a user interacts with a control, the controll calls {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$setViewValue NgModelController#$setViewValue}.
|
||||
This in turn pipelines all functions in {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$parsers NgModelController#$parsers} array, so that each of these functions has an opportunity to convert the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$setValidity NgModelController#$setValidity}.
|
||||
In a similar way, whenever a user interacts with a control, the controll calls {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setViewValue NgModelController#$setViewValue}.
|
||||
This in turn pipelines all functions in {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$parsers NgModelController#$parsers} array, so that each of these functions has an opportunity to convert the value and change validity state of the form control through {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
|
||||
|
||||
In the following example we create two directives.
|
||||
|
||||
@@ -276,13 +272,13 @@ In the following example we create two directives.
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Implementing custom form control (using ng-model)
|
||||
# Implementing custom form control (using `ngModel`)
|
||||
Angular implements all of the basic HTML form controls ({@link api/angular.module.ng.$compileProvider.directive.input input}, {@link api/angular.module.ng.$compileProvider.directive.select select}, {@link api/angular.module.ng.$compileProvider.directive.textarea textarea}), which should be sufficient for most cases.
|
||||
However, if you need more flexibility, you can write your own form control as a directive.
|
||||
|
||||
In order for custom control to work with `ng-model` and to achieve two-way data-binding it needs to:
|
||||
In order for custom control to work with `ngModel` and to achieve two-way data-binding it needs to:
|
||||
|
||||
- implement `render` method, which is responsible for rendering the data after it passed the {@link api/angular.module.ng.$compileProvider.directive.ng-model.NgModelController#$formatters NgModelController#$formatters},
|
||||
- implement `render` method, which is responsible for rendering the data after it passed the {@link api/angular.module.ng.$compileProvider.directive.ngModel.NgModelController#$formatters NgModelController#$formatters},
|
||||
- call `$setViewValue` method, whenever the user interacts with the control and model needs to be updated. This is usually done inside a DOM Event listener.
|
||||
|
||||
See {@link api/angular.module.ng.$compileProvider.directive $compileProvider.directive} for more info.
|
||||
|
||||
@@ -22,7 +22,7 @@ http://docs.angularjs.org/#!/api/angular.module.ng.$filter.number number} and {@
|
||||
http://docs.angularjs.org/#!/api/angular.module.ng.$filter.currency currency} filters.
|
||||
|
||||
Additionally, Angular supports localizable pluralization support provided by the {@link
|
||||
api/angular.module.ng.$compileProvider.directive.ng-pluralize ng-pluralize directive}.
|
||||
api/angular.module.ng.$compileProvider.directive.ngPluralize ngPluralize directive}.
|
||||
|
||||
All localizable Angular components depend on locale-specific rule sets managed by the {@link
|
||||
api/angular.module.ng.$locale $locale service}.
|
||||
|
||||
@@ -30,10 +30,6 @@ function GreetingCtrl($scope) {
|
||||
|
||||
The `GreetingCtrl` controller creates a `greeting` model which can be referred to in a template.
|
||||
|
||||
When a controller function is applied to an angular scope object, the `this` of the controller
|
||||
function becomes the scope of the angular scope object, so any assignment to `this` within the
|
||||
controller function happens on the angular scope object.
|
||||
|
||||
# Adding Behavior to a Scope Object
|
||||
|
||||
Behavior on an angular scope object is in the form of scope method properties available to the
|
||||
@@ -41,13 +37,8 @@ template/view. This behavior interacts with and modifies the application model.
|
||||
|
||||
As discussed in the {@link dev_guide.mvc.understanding_model Model} section of this guide, any
|
||||
objects (or primitives) assigned to the scope become model properties. Any functions assigned to
|
||||
the scope, along with any prototype methods of the controller type, become functions available in
|
||||
the template/view, and can be invoked via angular expressions and `ng-` event handlers (e.g. {@link
|
||||
api/angular.module.ng.$compileProvider.directive.ng-click ng-click}). These controller methods are always evaluated within the
|
||||
context of the angular scope object that the controller function was applied to (which means that
|
||||
the `this` keyword of any controller method is always bound to the scope that the controller
|
||||
augments). This is how the second task of adding behavior to the scope is accomplished.
|
||||
|
||||
the scope are available in the template/view, and can be invoked via angular expressions
|
||||
and `ng` event handler directives (e.g. {@link api/angular.module.ng.$compileProvider.directive.ngClick ngClick}).
|
||||
|
||||
# Using Controllers Correctly
|
||||
|
||||
@@ -78,7 +69,7 @@ instances).
|
||||
# Associating Controllers with Angular Scope Objects
|
||||
|
||||
You can associate controllers with scope objects explicitly via the {@link api/angular.module.ng.$rootScope.Scope#$new
|
||||
scope.$new} api or implicitly via the {@link api/angular.module.ng.$compileProvider.directive.ng-controller ng-controller
|
||||
scope.$new} api or implicitly via the {@link api/angular.module.ng.$compileProvider.directive.ngController ngController
|
||||
directive} or {@link api/angular.module.ng.$route $route service}.
|
||||
|
||||
|
||||
@@ -108,28 +99,34 @@ string "very". Depending on which button is clicked, the `spice` model is set to
|
||||
function SpicyCtrl($scope) {
|
||||
$scope.spice = 'very';
|
||||
$scope.chiliSpicy = function() {
|
||||
this.spice = 'chili';
|
||||
$scope.spice = 'chili';
|
||||
}
|
||||
$scope.jalapenoSpicy = function() {
|
||||
$scope.spice = 'jalapeño';
|
||||
}
|
||||
}
|
||||
|
||||
SpicyCtrl.prototype.jalapenoSpicy = function() {
|
||||
this.spice = 'jalapeño';
|
||||
}
|
||||
|
||||
</pre>
|
||||
|
||||
Things to notice in the example above:
|
||||
|
||||
- The `ng-controller` directive is used to (implicitly) create a scope for our template, and the
|
||||
- The `ngController` directive is used to (implicitly) create a scope for our template, and the
|
||||
scope is augmented (managed) by the `SpicyCtrl` controller.
|
||||
- `SpicyCtrl` is just a plain JavaScript function. As an (optional) naming convention the name
|
||||
starts with capital letter and ends with "Ctrl" or "Controller".
|
||||
- The JavaScript keyword `this` in the `SpicyCtrl` function is bound to the scope that the
|
||||
controller augments.
|
||||
- Assigning a property to `this` creates or updates the model.
|
||||
- Controller methods can be created through direct assignment to scope (the `chiliSpicy` method) or
|
||||
as prototype methods of the controller constructor function(the `jalapenoSpicy` method)
|
||||
- Assigning a property to `$scope` creates or updates the model.
|
||||
- Controller methods can be created through direct assignment to scope (the `chiliSpicy` method)
|
||||
- Both controller methods are available in the template (for the `body` element and and its
|
||||
children).
|
||||
- NB: Previous versions of Angular (pre 1.0 RC) allowed you to use `this` interchangeably with
|
||||
the $scope method, but this is no longer the case. Inside of methods defined on the scope
|
||||
`this` and $scope are interchangeable (angular sets `this` to $scope), but not otherwise
|
||||
inside your controller constructor.
|
||||
- NB: Previous versions of Angular (pre 1.0 RC) added prototype methods into the scope
|
||||
automatically, but this is no longer the case; all methods need to be added manually to
|
||||
the scope.
|
||||
|
||||
|
||||
Controller methods can also take arguments, as demonstrated in the following variation of the
|
||||
previous example.
|
||||
@@ -147,7 +144,7 @@ previous example.
|
||||
function SpicyCtrl($scope) {
|
||||
$scope.spice = 'very';
|
||||
$scope.spicy = function(spice) {
|
||||
this.spice = spice;
|
||||
$scope.spice = spice;
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
@@ -186,7 +183,7 @@ function BabyCtrl($scope) {
|
||||
}
|
||||
</pre>
|
||||
|
||||
Notice how we nested three `ng-controller` directives in our template. This template construct will
|
||||
Notice how we nested three `ngController` directives in our template. This template construct will
|
||||
result in 4 scopes being created for our view:
|
||||
|
||||
- The root scope
|
||||
@@ -206,19 +203,17 @@ are applied to the scope object.
|
||||
|
||||
## Testing Controllers
|
||||
|
||||
The way to test a controller depends upon how complicated the controller is.
|
||||
|
||||
- If your controller doesn't use DI or scope methods — create the controller with the `new`
|
||||
operator and test away. For example:
|
||||
Although there are many ways to test a controller, one of the best conventions, shown below,
|
||||
involves injecting the `$rootScope` and `$controller`
|
||||
|
||||
Controller Function:
|
||||
<pre>
|
||||
function myController() {
|
||||
this.spices = [{"name":"pasilla", "spiciness":"mild"},
|
||||
function myController($scope) {
|
||||
$scope.spices = [{"name":"pasilla", "spiciness":"mild"},
|
||||
{"name":"jalapeno", "spiceiness":"hot hot hot!"},
|
||||
{"name":"habanero", "spiceness":"LAVA HOT!!"}];
|
||||
|
||||
this.spice = "habanero";
|
||||
$scope.spice = "habanero";
|
||||
}
|
||||
</pre>
|
||||
|
||||
@@ -227,27 +222,51 @@ Controller Test:
|
||||
describe('myController function', function() {
|
||||
|
||||
describe('myController', function() {
|
||||
var ctrl;
|
||||
var scope;
|
||||
|
||||
beforeEach(function() {
|
||||
ctrl = new myController();
|
||||
});
|
||||
beforeEach(inject(function($rootScope, $controller) {
|
||||
scope = $rootScope.$new();
|
||||
var ctrl = $controller(myController, {$scope: scope});
|
||||
}));
|
||||
|
||||
it('should create "spices" model with 3 spices', function() {
|
||||
expect(ctrl.spices.length).toBe(3);
|
||||
expect(scope.spices.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should set the default value of spice', function() {
|
||||
expect(ctrl.spice).toBe('habanero');
|
||||
expect(scope.spice).toBe('habanero');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
- If your controller does use DI or scope methods — create a root scope, then create the controller
|
||||
in the root scope with `scope.$new(MyController)`. Test the controller using `$eval`, if necessary.
|
||||
- If you need to test a nested controller that depends on its parent's state — create a root scope,
|
||||
create a parent scope, create a child scope, and test the controller using $eval if necessary.
|
||||
|
||||
If you need to test a nested controller one needs to create the same scope hierarchy
|
||||
in your test as exist in the DOM.
|
||||
|
||||
<pre>
|
||||
describe('state', function() {
|
||||
var mainScope, childScope, babyScope;
|
||||
|
||||
beforeEach(inject(function($rootScope, $controller) {
|
||||
mainScope = $rootScope.$new();
|
||||
var mainCtrl = $controller(MainCtrl, {$scope: mainScope});
|
||||
childScope = mainScope.$new();
|
||||
var childCtrl = $controller(ChildCtrl, {$scope: childScope});
|
||||
babyScope = $rootScope.$new();
|
||||
var babyCtrl = $controller(BabyCtrl, {$scope: babyScope});
|
||||
}));
|
||||
|
||||
it('should have over and selected', function() {
|
||||
expect(mainScope.timeOfDay).toBe('morning');
|
||||
expect(mainScope.name).toBe('Nikki');
|
||||
expect(childScope.timeOfDay).toBe('morning');
|
||||
expect(childScope.name).toBe('Mattie');
|
||||
expect(babyScope.timeOfDay).toBe('evening');
|
||||
expect(babyScope.name).toBe('Gingerbreak Baby');
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
@@ -30,7 +30,7 @@ occurs in controllers:
|
||||
|
||||
<button ng-click="{{foos='ball'}}">Click me</button>
|
||||
|
||||
* Use {@link api/angular.module.ng.$compileProvider.directive.ng-init ng-init directive} in templates (for toy/example apps
|
||||
* Use {@link api/angular.module.ng.$compileProvider.directive.ngInit ngInit directive} in templates (for toy/example apps
|
||||
only, not recommended for real applications):
|
||||
|
||||
<body ng-init=" foo = 'bar' ">
|
||||
@@ -45,7 +45,7 @@ when processing the following template constructs:
|
||||
The code above creates a model called "query" on the current scope with the value set to "fluffy
|
||||
cloud".
|
||||
|
||||
* An iterator declaration in {@link api/angular.module.ng.$compileProvider.directive.ng-repeat ng-repeater}:
|
||||
* An iterator declaration in {@link api/angular.module.ng.$compileProvider.directive.ngRepeat ngRepeater}:
|
||||
|
||||
<p ng-repeat="phone in phones"></p>
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ the DOM based on information in the template, controller and model.
|
||||
|
||||
In the angular implementation of MVC, the view has knowledge of both the model and the controller.
|
||||
The view knows about the model where two-way data-binding occurs. The view has knowledge of the
|
||||
controller through angular directives, such as {@link api/angular.module.ng.$compileProvider.directive.ng-controller
|
||||
ng-controller} and {@link api/angular.module.ng.$compileProvider.directive.ng-view ng-view}, and through bindings of this form:
|
||||
controller through angular directives, such as {@link api/angular.module.ng.$compileProvider.directive.ngController
|
||||
ngController} and {@link api/angular.module.ng.$compileProvider.directive.ngView ngView}, and through bindings of this form:
|
||||
`{{someControllerFunction()}}`. In these ways, the view can call functions in an associated
|
||||
controller function.
|
||||
|
||||
|
||||
@@ -79,8 +79,8 @@ easier a web developer's life can if they're using angular:
|
||||
Try out the Live Preview above, and then let's walk through the example and describe what's going
|
||||
on.
|
||||
|
||||
In the `<html>` tag we specify that this is an angular application with the `ng-app` directive.
|
||||
The `ng-app' will cause the angular to {@link dev_guide.bootstrap auto initialize} your application.
|
||||
In the `<html>` tag we specify that this is an angular application with the `ngApp` directive.
|
||||
The `ngApp' will cause the angular to {@link dev_guide.bootstrap auto initialize} your application.
|
||||
|
||||
<html ng-app>
|
||||
|
||||
@@ -88,7 +88,7 @@ We load the angular using the `<script>` tag:
|
||||
|
||||
<script src="http://code.angularjs.org/angular-?.?.?.min.js"></script>
|
||||
|
||||
From the `ng-model` attribute of the `<input>` tags, angular automatically sets up two-way data
|
||||
From the `ngModel` attribute of the `<input>` tags, angular automatically sets up two-way data
|
||||
binding, and we also demonstrate some easy input validation:
|
||||
|
||||
Quantity: <input type="integer" min="0" ng-model="qty" required >
|
||||
@@ -112,7 +112,7 @@ And finally, the mysterious `{{ double curly braces }}`:
|
||||
This notation, `{{ _expression_ }}`, is a bit of built-in angular binding markup, a shortcut for
|
||||
displaying data to the user. The expression within curly braces is monitored and its evaluated value
|
||||
is updated into the view by angular's template compiler. Alternatively, one could use angular's
|
||||
{@link api/angular.module.ng.$compileProvider.directive.ng-bind ng-bind}) directive. The expression
|
||||
{@link api/angular.module.ng.$compileProvider.directive.ngBind ngBind}) directive. The expression
|
||||
itself can be a combination of both an expression and a {@link dev_guide.templates.filters filter}:
|
||||
`{{ expression | filter }}`. Angular provides filters for formatting display data.
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ recursively check the parent scope, grandparent scope, etc. all the way to the r
|
||||
defaulting to undefined.
|
||||
|
||||
{@link api/angular.module.ng.$compileProvider.directive directives} associated with elements
|
||||
(ng-controller, ng-repeat, ng-include, etc.) create new child scopes that inherit properties from
|
||||
(ngController, ngRepeat, ngInclude, etc.) create new child scopes that inherit properties from
|
||||
the current parent scope. Any code in Angular is free to create a new scope. Whether or not your
|
||||
code does so is an implementation detail of the directive, that is, you can decide when or if this
|
||||
happens. Inheritance typically mimics HTML DOM element nesting, but does not do so with the same
|
||||
@@ -123,8 +123,8 @@ usually fall into one of two categories:
|
||||
expressions `{{expression}}`, register listeners using the {@link
|
||||
api/angular.module.ng.$rootScope.Scope#$watch $watch()} method. This type of directive needs to
|
||||
be notified whenever the expression changes so that it can update the view.
|
||||
- Listener directives, such as {@link api/angular.module.ng.$compileProvider.directive.ng-click
|
||||
ng-click}, register a listener with the DOM. When the DOM listener fires, the directive executes
|
||||
- Listener directives, such as {@link api/angular.module.ng.$compileProvider.directive.ngClick
|
||||
ngClick}, register a listener with the DOM. When the DOM listener fires, the directive executes
|
||||
the associated expression and updates the view using the {@link
|
||||
api/angular.module.ng.$rootScope.Scope#$apply $apply()} method.
|
||||
3. When an external event (such as a user action, timer or XHR) is received, the associated {@link
|
||||
@@ -134,8 +134,8 @@ api/angular.module.ng.$rootScope.Scope#$apply $apply()} method so that all liste
|
||||
|
||||
### Directives that create scopes
|
||||
In most cases, {@link api/angular.module.ng.$compileProvider.directive directives} and scopes interact but do not create new
|
||||
instances of scope. However, some directives, such as {@link api/angular.module.ng.$compileProvider.directive.ng-controller
|
||||
ng-controller} and {@link api/angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat}, create new child scopes using
|
||||
instances of scope. However, some directives, such as {@link api/angular.module.ng.$compileProvider.directive.ngController
|
||||
ngController} and {@link api/angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}, create new child scopes using
|
||||
the {@link api/angular.module.ng.$rootScope.Scope#$new $new()} method and then attach the child scope to the
|
||||
corresponding DOM element. You can retrieve a scope for any DOM element by using an
|
||||
`angular.element(aDomElement).scope()` method call.)
|
||||
@@ -144,7 +144,7 @@ corresponding DOM element. You can retrieve a scope for any DOM element by using
|
||||
### Controllers and scopes
|
||||
Scopes and controllers interact with each other in the following situations:
|
||||
- Controllers use scopes to expose controller methods to templates (see {@link
|
||||
api/angular.module.ng.$compileProvider.directive.ng-controller ng-controller}).
|
||||
api/angular.module.ng.$compileProvider.directive.ngController ngController}).
|
||||
- Controllers define methods (behavior) that can mutate the model (properties on the scope).
|
||||
- Controllers may register {@link api/angular.module.ng.$rootScope.Scope#$watch watches} on the model. These watches
|
||||
execute immediately after the controller behavior executes, but before the DOM gets updated.
|
||||
@@ -170,7 +170,7 @@ $watch-ers firing and view getting updated. Similarly, when a request to fetch d
|
||||
is made and the response comes back, the data is written into the model (scope) within an $apply,
|
||||
which then pushes updates through to the view and any other dependents.
|
||||
|
||||
A widget that creates scopes (such as {@link api/angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat}) via `$new`,
|
||||
A widget that creates scopes (such as {@link api/angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat}) via `$new`,
|
||||
doesn't need to worry about propagating the `$digest` call from the parent scope to child scopes.
|
||||
This happens automatically.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ the contexts in which Angular creates data-bindings between the model and the vi
|
||||
In addition to providing the context in which data is evaluated, Angular scope objects watch for
|
||||
model changes. The scope objects also notify all components interested in any model changes (for
|
||||
example, functions registered through {@link api/angular.module.ng.$rootScope.Scope#$watch $watch}, bindings created by
|
||||
{@link api/angular.module.ng.$compileProvider.directive.ng-bind ng-bind}, or HTML input elements).
|
||||
{@link api/angular.module.ng.$compileProvider.directive.ngBind ngBind}, or HTML input elements).
|
||||
|
||||
Angular scope objects:
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ code, observe how the value of `name` changes, based on the HTML element it is d
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
The angular {@link api/angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} directive creates a new scope for each
|
||||
The angular {@link api/angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} directive creates a new scope for each
|
||||
element that it repeats (in this example the elements are list items). In the `<ul>` element, we
|
||||
initialized `name` to "Hank", and we created an array called `names` to use as the data source for
|
||||
the list items. In each `<li>` element, `name` is overridden. Outside of the `<li>` repeater, the
|
||||
|
||||
@@ -305,7 +305,7 @@ history API or not; the `$location` service makes this transparent to you.
|
||||
### Html link rewriting
|
||||
|
||||
When you use the history API mode, you will need different links in different browser, but all you
|
||||
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
|
||||
When a user clicks on this link,
|
||||
|
||||
@@ -316,12 +316,13 @@ When a user clicks on this link,
|
||||
In cases like the following, links are not rewritten; instead, the browser will perform a full page
|
||||
reload to the original link.
|
||||
|
||||
- Links with an `ng-ext-link` directive<br />
|
||||
Example: `<a href="/ext/link?a=b" ng-ext-link>link</a>`
|
||||
- Links that contain `target="_blank"`<br />
|
||||
Example: `<a href="/ext/link?a=b" target="_blank">link</a>`
|
||||
- Absolute links that go to a different domain<br />
|
||||
- Links that contain `target` element<br>
|
||||
Example: `<a href="/ext/link?a=b" target="_self">link</a>`
|
||||
- Absolute links that go to a different domain<br>
|
||||
Example: `<a href="http://angularjs.org/">link</a>`
|
||||
- Links starting with '/' that lead to a different base path when base is defined<br>
|
||||
Example: `<a href="/not-my-base/link">link</a>`
|
||||
|
||||
|
||||
### Server side
|
||||
|
||||
@@ -341,11 +342,13 @@ Applications Crawlable}.
|
||||
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. You must use an absolute path because the
|
||||
path is going to be rewritten. You can use `<base href="" />` tag as well.
|
||||
Be sure to check all relative links, images, scripts etc. You must either specify the url base in
|
||||
the head of your main html file (`<base href="/my-base">`) or you must use absolute urls
|
||||
(starting with `/`) everywhere because relative urls will be resolved to absolute urls using the
|
||||
initial absolute url of the document, which is often different from the root of the application.
|
||||
|
||||
Running Angular apps with the History API enabled from document root is strongly encouraged as it
|
||||
takes care of all relative link issues. **Otherwise you have to specify <base href="" /> !**
|
||||
takes care of all relative link issues.
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
@@ -372,30 +375,30 @@ In this examples we use `<base href="/base/index.html" />`
|
||||
<div ng-non-bindable class="html5-hashbang-example">
|
||||
<div id="html5-mode" ng-controller="Html5Cntl">
|
||||
<h3>Browser with History API</h3>
|
||||
<ng-address-bar browser="html5"></ng-address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> |
|
||||
<a href="sec/ond?flag#hash">sec/ond?flag#hash</a> |
|
||||
<a href="/base/another?search" ng-ext-link>external</a>
|
||||
<div ng-address-bar browser="html5"></div><br><br>
|
||||
$location.protocol() = {{$location.protocol()}}<br>
|
||||
$location.host() = {{$location.host()}}<br>
|
||||
$location.port() = {{$location.port()}}<br>
|
||||
$location.path() = {{$location.path()}}<br>
|
||||
$location.search() = {{$location.search()}}<br>
|
||||
$location.hash() = {{$location.hash()}}<br>
|
||||
<a href="http://www.host.com/base/first?a=b">/base/first?a=b</a> |
|
||||
<a href="http://www.host.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
|
||||
<a href="/other-base/another?search">external</a>
|
||||
</div>
|
||||
|
||||
<div id="hashbang-mode" ng-controller="HashbangCntl">
|
||||
<h3>Browser without History API</h3>
|
||||
<ng-address-bar browser="hashbang"></ng-address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> |
|
||||
<a href="sec/ond?flag#hash">sec/ond?flag#hash</a> |
|
||||
<a href="/base/another?search" ng-ext-link>external</a>
|
||||
<div ng-address-bar browser="hashbang"></div><br><br>
|
||||
$location.protocol() = {{$location.protocol()}}<br>
|
||||
$location.host() = {{$location.host()}}<br>
|
||||
$location.port() = {{$location.port()}}<br>
|
||||
$location.path() = {{$location.path()}}<br>
|
||||
$location.search() = {{$location.search()}}<br>
|
||||
$location.hash() = {{$location.hash()}}<br>
|
||||
<a href="http://www.host.com/base/first?a=b">/base/first?a=b</a> |
|
||||
<a href="http://www.host.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
|
||||
<a href="/other-base/another?search">external</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -417,7 +420,6 @@ In this examples we use `<base href="/base/index.html" />`
|
||||
return baseHref;
|
||||
};
|
||||
|
||||
this.hover = angular.noop;
|
||||
this.notifyWhenOutstandingRequests = angular.noop;
|
||||
}
|
||||
|
||||
@@ -426,20 +428,19 @@ In this examples we use `<base href="/base/index.html" />`
|
||||
hashbang: new FakeBrowser('http://www.host.com/base/index.html#!/path?a=b#h', '/base/index.html')
|
||||
};
|
||||
|
||||
function Html5Cntl($location) {
|
||||
this.$location = $location;
|
||||
function Html5Cntl($scope, $location) {
|
||||
$scope.$location = $location;
|
||||
}
|
||||
|
||||
function HashbangCntl($location) {
|
||||
this.$location = $location;
|
||||
function HashbangCntl($scope, $location) {
|
||||
$scope.$location = $location;
|
||||
}
|
||||
|
||||
function initEnv(name) {
|
||||
var root = angular.element(document.getElementById(name + '-mode'));
|
||||
angular.bootstrap(root, [function($compileProvider, $locationProvider, $provide){
|
||||
$locationProvider.html5Mode = true;
|
||||
$locationProvider.hashPrefix = '!';
|
||||
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
|
||||
$provide.value('$browser', browsers[name]);
|
||||
$provide.value('$document', root);
|
||||
$provide.value('$sniffer', {history: name == 'html5'});
|
||||
@@ -447,7 +448,7 @@ In this examples we use `<base href="/base/index.html" />`
|
||||
$compileProvider.directive('ngAddressBar', function() {
|
||||
return function(scope, elm, attrs) {
|
||||
var browser = browsers[attrs.browser],
|
||||
input = angular.element('<input type="text" />').val(browser.url()),
|
||||
input = angular.element('<input type="text">').val(browser.url()),
|
||||
delay;
|
||||
|
||||
input.bind('keypress keyup keydown', function() {
|
||||
|
||||
@@ -9,10 +9,10 @@ Angular sets these CSS classes. It is up to your application to provide useful s
|
||||
|
||||
* `ng-invalid`, `ng-valid`
|
||||
- **Usage:** angular applies this class to an input widget element if that element's input does
|
||||
notpass validation. (see {@link api/angular.module.ng.$compileProvider.directive.input input} widget).
|
||||
notpass validation. (see {@link api/angular.module.ng.$compileProvider.directive.input input} directive).
|
||||
|
||||
* `ng-pristine`, `ng-dirty`
|
||||
- **Usage:** angular {@link api/angular.module.ng.$compileProvider.directive.input input} widget applies `ng-pristine` class
|
||||
- **Usage:** angular {@link api/angular.module.ng.$compileProvider.directive.input input} directive applies `ng-pristine` class
|
||||
to a new input widget element which did not have user interaction. Once the user interacts with
|
||||
the input widget the class is changed to `ng-dirty`.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ with {@link dev_guide.expressions expressions}:
|
||||
|
||||
<pre>
|
||||
<html ng-app>
|
||||
<!-- Body tag augmented with ng-controller directive -->
|
||||
<!-- Body tag augmented with ngController directive -->
|
||||
<body ng-controller="MyController">
|
||||
<input ng-model="foo" value="bar">
|
||||
<!-- Button tag with ng-click directive, and
|
||||
@@ -42,7 +42,7 @@ In a simple single-page app, the template consists of HTML, CSS, and angular dir
|
||||
in just one HTML file (usually `index.html`). In a more complex app, you can display multiple views
|
||||
within one main page using "partials", which are segments of template located in separate HTML
|
||||
files. You "include" the partials in the main page using the {@link api/angular.module.ng.$route
|
||||
$route} service in conjunction with the {@link api/angular.module.ng.$compileProvider.directive.ng-view ng-view} directive. An
|
||||
$route} service in conjunction with the {@link api/angular.module.ng.$compileProvider.directive.ngView ngView} directive. An
|
||||
example of this technique is shown in the {@link tutorial/ angular tutorial}, in steps seven and
|
||||
eight.
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ State & Singletons}
|
||||
|
||||
The class above is hard to test since we have to change global state:
|
||||
<pre>
|
||||
var oldXHR = glabal.xhr;
|
||||
glabal.xhr = function mockXHR() {};
|
||||
var oldXHR = global.xhr;
|
||||
global.xhr = function mockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
@@ -126,12 +126,12 @@ there is only one global variable to be reset).
|
||||
|
||||
The class above is hard to test since we have to change global state:
|
||||
<pre>
|
||||
var oldServiceLocator = glabal.serviceLocator;
|
||||
glabal.serviceLocator.set('xhr', function mockXHR() {});
|
||||
var oldServiceLocator = global.serviceLocator;
|
||||
global.serviceLocator.set('xhr', function mockXHR() {});
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
glabal.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
|
||||
global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ modules require it.
|
||||
Modules are a way of managing $injector configuration, and have nothing to do with loading of
|
||||
scripts into a VM. There are existing projects which deal with script loading, which may be used
|
||||
with Angular. Because modules do nothing at load time they can be loaded into the VM in any order
|
||||
and thus script loaders can take advantage of this property and paralyze the loading process.
|
||||
and thus script loaders can take advantage of this property and parallelize the loading process.
|
||||
|
||||
|
||||
# Unit Testing
|
||||
@@ -232,7 +232,7 @@ describe('myApp', function() {
|
||||
$provide.value('$window', {
|
||||
alert: jasmine.createSpy('alert')
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
// The inject() will create the injector and inject the greet and
|
||||
// $window into the tests. The test need not concern itself with
|
||||
@@ -251,7 +251,7 @@ describe('myApp', function() {
|
||||
});
|
||||
inject(function(greet) {
|
||||
greet('World');
|
||||
expect(alertSpy).toHaveBeenCalledWith('World');
|
||||
expect(alertSpy).toHaveBeenCalledWith('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,15 +65,15 @@ This example demonstrates angular's two-way data binding:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" ng-model="yourname" value="World"/>
|
||||
<hr/>
|
||||
Hello {{yourname}}!
|
||||
Your name: <input type="text" ng-model="yourname" placeholder="World">
|
||||
<hr>
|
||||
Hello {{yourname || 'World'}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
After the refresh, the page should look something like this:
|
||||
|
||||
<img class="left" src="img/helloworld_2way.png" border="1" />
|
||||
<img class="left" src="img/helloworld_2way.png" border="1" >
|
||||
|
||||
These are some of the important points to note from this example:
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial
|
||||
@description
|
||||
|
||||
A great way to get introduced to AngularJS 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" width="488" height="413">
|
||||
|
||||
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:
|
||||
|
||||
* 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 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 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 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 AngularJS, check out the
|
||||
{@link misc/started Getting Started} document.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Working with the code
|
||||
|
||||
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">
|
||||
<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>
|
||||
<pre><code>java -version</code></pre>
|
||||
<p>You will need Java to run unit tests.</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 the following command:</p>
|
||||
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
|
||||
<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 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="http://nodejs.org/#download">install 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>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></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 the following command:</p>
|
||||
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
|
||||
<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 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> should be
|
||||
executed from the Windows command line.</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/#download">node.js</a>. Make sure that
|
||||
<code>nodejs\bin</code> was added 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>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>
|
||||
<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 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="http://nodejs.org/#download">install 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>
|
||||
|
||||
<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 the following command in the
|
||||
Windows command line:</p>
|
||||
<pre><code>java -version</code></pre>
|
||||
<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 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/#download">node.js</a>. Make sure that
|
||||
<code>nodejs\bin</code> was added 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>
|
||||
|
||||
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 some cool stuff done!
|
||||
|
||||
{@link step_00 <span class="tutorial-start">Get Started!</span>}
|
||||
@@ -0,0 +1,275 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 0 - Bootstrapping
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="0"></ul>
|
||||
|
||||
|
||||
You are now ready to build the AngularJS phonecat app. 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.
|
||||
|
||||
|
||||
<doc:tutorial-instructions show="true">
|
||||
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
|
||||
<ol>
|
||||
<li><p>In angular-phonecat directory, run this command:</p>
|
||||
<pre><code>git checkout -f step-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
|
||||
the number of the step you are on. This will cause any changes you made within
|
||||
your working directory to be lost.</p></li>
|
||||
|
||||
<li>To see the app running in a browser, do one of the following:
|
||||
<ul>
|
||||
<li><b>For node.js users:</b>
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run
|
||||
<code>./scripts/web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a
|
||||
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
<ol>
|
||||
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
|
||||
directory.</li>
|
||||
<li>Navigate in your browser to
|
||||
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
|
||||
<doc:tutorial-instruction id="git-win" title="Git on Windows">
|
||||
<ol>
|
||||
<li><p>Open msysGit bash and run this command (in angular-phonecat directory):</p>
|
||||
<pre><code>git checkout -f step-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
|
||||
the number of the step you are on. This will cause any changes you made within
|
||||
your working directory to be lost.</p></li>
|
||||
<li>To see the app running in a browser, do one of the following:
|
||||
<ul>
|
||||
<li><b>For node.js users:</b>
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run <code>node
|
||||
scripts\web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a
|
||||
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
<ol>
|
||||
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
|
||||
directory.</li>
|
||||
<li>Navigate in your browser to
|
||||
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
|
||||
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
|
||||
<ol>
|
||||
<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
|
||||
the number of the step you are on. This will cause any changes you made within
|
||||
your working directory to be lost.</p></li>
|
||||
<li>To see the app running in a browser, do one of the following:
|
||||
<ul>
|
||||
<li><b>For node.js users:</b>
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run
|
||||
<code>./scripts/web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a
|
||||
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
<ol>
|
||||
<li>Configure the server to serve the files in the angular-phonecat
|
||||
<code>sandbox</code> directory.</li>
|
||||
<li>Navigate in your browser to
|
||||
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
|
||||
|
||||
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
|
||||
<ol>
|
||||
<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
|
||||
the number of the step you are on. This will cause any changes you made within
|
||||
your working directory to be lost.</p></li>
|
||||
<li>To see the app running in a browser, do one of the following:
|
||||
<ul>
|
||||
<li><b>For node.js users:</b>
|
||||
<ol>
|
||||
<li>In a <i>separate</i> terminal tab or window, run <code>node
|
||||
scripts\web-server.js</code> to start the web server.</li>
|
||||
<li>Open a browser window for the app and navigate to <a
|
||||
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
|
||||
</ol>
|
||||
</li>
|
||||
<li><b>For other http servers:</b>
|
||||
<ol>
|
||||
<li>Configure the server to serve the files in the angular-phonecat
|
||||
<code>sandbox</code> directory.</li>
|
||||
<li>Navigate in your browser to
|
||||
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</doc:tutorial-instruction>
|
||||
</doc:tutorial-instructions>
|
||||
|
||||
|
||||
You can now see the page in your browser. It's not very exciting, but that's OK.
|
||||
|
||||
The 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.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html lang="en" ng-app>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>My HTML File</title>
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
<link rel="stylesheet" href="css/bootstrap.css">
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Nothing here {{'yet' + '!'}}</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
## What is the code doing?
|
||||
|
||||
* `ng-app` directive:
|
||||
|
||||
<html ng-app>
|
||||
|
||||
The `ng-app` attribute is represents an Angular directive used to flag an element which Angular
|
||||
should consider to be the root element of our application. This gives application developers the
|
||||
freedom to tell Angular if the entire html page or only a portion of it should be treated as the
|
||||
Angular application.
|
||||
|
||||
* AngularJS script tag:
|
||||
|
||||
<script src="lib/angular/angular.js">
|
||||
|
||||
This 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.module.ng.$compileProvider.directive.ngApp ngApp} directive. If
|
||||
Angular finds the directive, it will bootstrap the application with the root of the application DOM
|
||||
being the element on which the `ngApp` directive was defined.
|
||||
|
||||
* Double-curly binding with an expression:
|
||||
|
||||
Nothing here {{'yet' + '!'}}`
|
||||
|
||||
This line demonstrates the core feature of Angular's templating capabilities – a binding, denoted
|
||||
by double-curlies `{{ }}` as well as a simple expression `'yet' + '!'` used in this binding.
|
||||
|
||||
The binding tells Angular, that it should evaluate an expression and insert the result into the
|
||||
DOM in place of the binding. Rather than a one-time insert, as we'll see in the next steps, a
|
||||
binding will result in efficient continuous updates whenever the result of the expression
|
||||
evaluation changes.
|
||||
|
||||
{@link guide/dev_guide.expressions Angular expression} is a JavaScript-like code snippet that is
|
||||
evaluated by Angular in the context of the current model scope, rather than within the scope of
|
||||
the global context (`window`).
|
||||
|
||||
As expected, once this template is processed by Angular, the html page will contains text:
|
||||
"Nothing here yet!".
|
||||
|
||||
## Bootstrapping AngularJS apps
|
||||
|
||||
Bootstrapping AngularJS apps automatically using the `ngApp` directive is very easy and suitable
|
||||
for most cases. In advanced cases, such as when using script loaders, you can use
|
||||
{@link guide/dev_guide.bootstrap.manual_bootstrap imperative / manual way} to bootstrap the app.
|
||||
|
||||
There are 3 important things that happen during the app bootstrap:
|
||||
|
||||
1. The {@link api/angular.module.AUTO.$injector injector} that will be used for dependency injection
|
||||
within this app is created.
|
||||
|
||||
2. The injector will then create the {@link api/angular.module.ng.$rootScope root scope} that will
|
||||
become the context for the model of our application.
|
||||
|
||||
3. Angular will then "compile" the DOM starting at the `ngApp` root element, processing any
|
||||
directives and bindings found along the way.
|
||||
|
||||
|
||||
Once an application is bootstrapped, it will then wait for incoming browser events (such as mouse
|
||||
click, key press or incoming HTTP response) that might change the model. Once such event occurs,
|
||||
Angular detects if it caused any model changes and if changes are found, Angular will reflect them
|
||||
in the view by updating all of the affected bindings.
|
||||
|
||||
The structure of our application is currently very simple. The template contains just one directive
|
||||
and one static binding, and our model is empty. That will soon change!
|
||||
|
||||
<img src="img/tutorial/tutorial_00.png">
|
||||
|
||||
|
||||
## What are all these files in my working directory?
|
||||
|
||||
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,
|
||||
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:
|
||||
|
||||
* Removed the example app
|
||||
* Added phone images to `app/img/phones/`
|
||||
* Added phone data files (JSON) to `app/phones/`
|
||||
* Added [Bootstrap](http://twitter.github.com/bootstrap/) files to `app/css/` and `app/img/`
|
||||
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Try adding a new expression to the `index.html` that will do some math:
|
||||
|
||||
<p>1 + 2 = {{ 1 + 2 }}</p>
|
||||
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
Now let's go to {@link step_01 step 1} and add some content to the web app.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="0"></ul>
|
||||
|
||||
<div style="display: none">
|
||||
Note: During the bootstrap the injector and the root scope will then be associated with the
|
||||
element on which the `ngApp` directive was declared, so when debugging the app you can retrieve
|
||||
them from browser console via `angular.element(rootElement).scope()` and
|
||||
`angular.element(rootElement).injector()`.
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 1 - Static Template
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="1"></ul>
|
||||
|
||||
|
||||
In order to illustrate how angular enhances standard HTML, you will create a purely *static* HTML
|
||||
page and then examine how we can turn this HTML code into a template that angular will use to
|
||||
dynamically display the same result with any set of data.
|
||||
|
||||
In this step you will add some basic information about two cell phones to an HTML page.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="1" show="true"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
The page now contains a list with information about two phones.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-0...step-1 GitHub}:
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<ul>
|
||||
<li>
|
||||
<span>Nexus S</span>
|
||||
<p>
|
||||
Fast just got faster with Nexus S.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<span>Motorola XOOM™ with Wi-Fi</span>
|
||||
<p>
|
||||
The Next, Next Generation tablet.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Try adding more static HTML to `index.html`. For example:
|
||||
|
||||
<p>Total number of phones: 2</p>
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
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 AngularJS to dynamically generate the same list.
|
||||
|
||||
|
||||
<ul doc:tutorial-nav="1"></ul>
|
||||
@@ -0,0 +1,212 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 2 - Angular Templates
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="2"></ul>
|
||||
|
||||
|
||||
Now it's time to make the web page dynamic — with AngularJS. 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. For Angular apps, we encourage the
|
||||
use of {@link http://en.wikipedia.org/wiki/Model–View–Controller 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 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}:
|
||||
|
||||
|
||||
## View and Template
|
||||
|
||||
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 view component is constructed by Angular from this template:
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
<html ng-app>
|
||||
<head>
|
||||
...
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="js/controllers.js"></script>
|
||||
</head>
|
||||
<body ng-controller="PhoneListCtrl">
|
||||
|
||||
<ul>
|
||||
<li ng-repeat="phone in phones">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
We replaced the hard-coded phone list with the
|
||||
{@link api/angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat directive} 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. The
|
||||
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
|
||||
tag as the template.
|
||||
|
||||
* As we've learned in step 0, the curly braces around `phone.name` and `phone.snippet` denote
|
||||
bindings. As opposed to evaluating constants, these expression are refering to our application
|
||||
model, which was set up in our `PhoneListCtrl` controller.
|
||||
|
||||
<img src="img/tutorial/tutorial_02.png">
|
||||
|
||||
|
||||
## Model and Controller
|
||||
|
||||
The data __model__ (a simple array of phones in object literal notation) is instantiated within
|
||||
the `PhoneListCtrl` __controller__:
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
function PhoneListCtrl($scope) {
|
||||
$scope.phones = [
|
||||
{"name": "Nexus S",
|
||||
"snippet": "Fast just got faster with Nexus S."},
|
||||
{"name": "Motorola XOOM™ with Wi-Fi",
|
||||
"snippet": "The Next, Next Generation tablet."},
|
||||
{"name": "MOTOROLA XOOM™",
|
||||
"snippet": "The Next, Next Generation tablet."}
|
||||
];
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
|
||||
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. We connected the dots between the presentation, data, and logic components
|
||||
as follows:
|
||||
|
||||
* `PhoneListCtrl` — the name of our controller function (located in the JavaScript file
|
||||
`controllers.js`), matches the value of the
|
||||
{@link api/angular.module.ng.$compileProvider.directive.ngController ngController} directive located
|
||||
on the `<body>` tag.
|
||||
|
||||
* The phone data is then attached to the *scope* (`$scope`) that was injected into our controller
|
||||
function. The controller scope is a prototypically descendant of the root scope that was created
|
||||
when the application bootstrapped. This controller scope is available to all bindings located within
|
||||
the `<body ng-controller="PhoneListCtrl">` tag.
|
||||
|
||||
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.module.ng.$rootScope.Scope angular scope documentation}.
|
||||
|
||||
|
||||
## Tests
|
||||
|
||||
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>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
|
||||
it('should create "phones" model with 3 phones', function() {
|
||||
var scope = {},
|
||||
ctrl = new PhoneListCtrl(scope);
|
||||
|
||||
expect(scope.phones.length).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
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 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:
|
||||
|
||||
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
|
||||
`./scripts/test-server.sh` to start the test web server.
|
||||
|
||||
2. Open a new browser window and navigate to {@link http://localhost:9876}.
|
||||
|
||||
3. Choose "Capture this browser in strict mode".
|
||||
|
||||
At this point, you can leave this window open and forget about it. JsTestDriver will use it to
|
||||
execute the tests and report the results in the terminal.
|
||||
|
||||
4. Execute the test by running `./scripts/test.sh`
|
||||
|
||||
You should see the following or similar output:
|
||||
|
||||
Chrome: Runner reset.
|
||||
.
|
||||
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms)
|
||||
Chrome 19.0.1084.36 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
|
||||
|
||||
Yay! The test passed! Or not...
|
||||
|
||||
Note: If you see errors after you run the test, close the browser window and go back to the
|
||||
terminal and kill the script, then repeat the procedure above.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Add another binding to `index.html`. For example:
|
||||
|
||||
<p>Total number of phones: {{phones.length}}</p>
|
||||
|
||||
* Create a new model property in the controller and bind to it from the template. For example:
|
||||
|
||||
$scope.hello = "Hello, World!"
|
||||
|
||||
Refresh your browser to make sure it says, "Hello, World!"
|
||||
|
||||
* Create a repeater that constructs a simple table:
|
||||
|
||||
<table>
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
|
||||
</table>
|
||||
|
||||
Now, make the list 1-based by incrementing `i` by one in the binding:
|
||||
|
||||
<table>
|
||||
<tr><th>row number</th></tr>
|
||||
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
|
||||
</table>
|
||||
|
||||
* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`, and rerun the
|
||||
`./scripts/test.sh` script.
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,196 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 3 - Filtering Repeaters
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="3"></ul>
|
||||
|
||||
|
||||
We did a lot of work in laying a foundation for the app in the last step, so now we'll do something
|
||||
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. Notice that the phone list on the page changes depending on what a
|
||||
user types into the search box.
|
||||
|
||||
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}:
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We made no changes to the controller.
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<!--Sidebar content-->
|
||||
|
||||
Search: <input ng-model="query">
|
||||
|
||||
</div>
|
||||
<div class="span10">
|
||||
<!--Body content-->
|
||||
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
...
|
||||
</pre>
|
||||
|
||||
We added a standard HTML `<input>` tag and used angular's
|
||||
{@link api/angular.module.ng.$filter.filter $filter} function to process the input for the
|
||||
`ngRepeate` directive.
|
||||
|
||||
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:
|
||||
|
||||
* 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
|
||||
available as a filter input in the list repeater (`phone in phones | filter:`__`query`__). When
|
||||
changes to the data model cause the repeater's input to change, the repeater efficiently updates
|
||||
the DOM to reflect the current state of the model.
|
||||
|
||||
<img src="img/tutorial/tutorial_03.png">
|
||||
|
||||
* Use of `filter` filter. The {@link api/angular.module.ng.$filter.filter filter} function uses the
|
||||
`query` value to create a new array that contains only those records that match the `query`.
|
||||
|
||||
`ngRepeat` automatically updates the view in response to the changing number of phones returned
|
||||
by the `filter` 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
|
||||
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.
|
||||
|
||||
The search feature was fully implemented via templates and data-binding, so we'll write our first
|
||||
end-to-end test, to verify that the feature works.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat App', function() {
|
||||
|
||||
describe('Phone list view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html');
|
||||
});
|
||||
|
||||
|
||||
it('should filter the phone list as user types into the search box', function() {
|
||||
expect(repeater('.phones li').count()).toBe(3);
|
||||
|
||||
input('query').enter('nexus');
|
||||
expect(repeater('.phones li').count()).toBe(1);
|
||||
|
||||
input('query').enter('motorola');
|
||||
expect(repeater('.phones li').count()).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
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 guide/dev_guide.e2e-testing Angular's end-to-end
|
||||
test runner}.
|
||||
|
||||
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:
|
||||
`http://localhost:[port-number]/[context-path]/test/e2e/runner.html`
|
||||
* 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
|
||||
really is that easy to set up any functional, readable, end-to-end test.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Display the current value of the `query` model by adding a `{{query}}` binding into the
|
||||
`index.html` template, and see how it changes when you type in the input box.
|
||||
|
||||
* Let's see how we can get the current value of the `query` model to appear in the HTML page title.
|
||||
|
||||
You might think you could just add the {{query}} to the title tag element as follows:
|
||||
|
||||
<title>Google Phone Gallery: {{query}}</title>
|
||||
|
||||
However, when you reload the page, you won't see the expected result. This is because the "query"
|
||||
model lives in the scope defined by the body element:
|
||||
|
||||
<body ng-controller="PhoneListCtrl">
|
||||
|
||||
If you want to bind to the query model from the `<title>` element, you must __move__ the
|
||||
`ngController` declaration to the HTML element because it is the common parent of both the body
|
||||
and title elements:
|
||||
|
||||
<html ng-app ng-controller="PhoneListCtrl">
|
||||
|
||||
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.module.ng.$compileProvider.directive.ngBind
|
||||
ngBind} or {@link api/angular.module.ng.$compileProvider.directive.ngBindTemplate
|
||||
ngBindTemplate} 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>
|
||||
it('should display the current filter value within an element with id "status"',
|
||||
function() {
|
||||
expect(element('#status').text()).toMatch(/Current filter: \s*$/);
|
||||
|
||||
input('query').enter('nexus');
|
||||
|
||||
expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
|
||||
|
||||
//alternative version of the last assertion that tests just the value of the binding
|
||||
using('#status').expect(binding('query')).toBe('nexus');
|
||||
});
|
||||
</pre>
|
||||
|
||||
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 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
|
||||
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 4 - Two-way Data Binding
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="4"></ul>
|
||||
|
||||
|
||||
In this step, you will add a feature to let your users control the order of the items in the phone
|
||||
list. The dynamic ordering is implemented by creating a new model property, wiring it together with
|
||||
the repeater, and letting the data binding magic do the rest of the work.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="4"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
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 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
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
Search: <input ng-model="query">
|
||||
Sort by:
|
||||
<select ng-model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
|
||||
...
|
||||
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
|
||||
{{phone.name}}
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
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.
|
||||
|
||||
<img src="img/tutorial/tutorial_04.png">
|
||||
|
||||
* We then chained the `filter` filter with {@link api/angular.module.ng.$filter.orderBy `orderBy`}
|
||||
filter to further process the input into the repeater. `orderBy` is a filter that takes an input
|
||||
array, copies it and reorders the copy which is then returned.
|
||||
|
||||
Angular creates a two way data-binding between the select element and the `orderProp` model.
|
||||
`orderProp` is then used as the input for the `orderBy` filter.
|
||||
|
||||
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
|
||||
data-binding will cause the view to automatically update. No bloated DOM manipulation code is
|
||||
necessary!
|
||||
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
__`app/js/controller.js`:__
|
||||
<pre>
|
||||
function PhoneListCtrl($scope) {
|
||||
$scope.phones = [
|
||||
{"name": "Nexus S",
|
||||
"snippet": "Fast just got faster with Nexus S.",
|
||||
"age": 0},
|
||||
{"name": "Motorola XOOM™ with Wi-Fi",
|
||||
"snippet": "The Next, Next Generation tablet.",
|
||||
"age": 1},
|
||||
{"name": "MOTOROLA XOOM™",
|
||||
"snippet": "The Next, Next Generation tablet.",
|
||||
"age": 2}
|
||||
];
|
||||
|
||||
$scope.orderProp = 'age';
|
||||
}
|
||||
</pre>
|
||||
|
||||
* We modified the `phones` model - the array of phones - and added an `age` property to each phone
|
||||
record. This property is used to order phones by age.
|
||||
|
||||
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
|
||||
not set the default value here, the model would stay uninitialized until our user would pick an
|
||||
option from the drop down menu.
|
||||
|
||||
This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
|
||||
browser, "Newest" is selected in the drop down menu. This is because we set `orderProp` to `'age'`
|
||||
in the controller. So the binding works in the direction from our model to the UI. Now if you
|
||||
select "Alphabetically" in the drop down menu, the model will be updated as well and the phones
|
||||
will be reordered. That is the data-binding doing its job in the opposite direction — from the UI
|
||||
to the model.
|
||||
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
The changes we made should be verified with both a unit test and an end-to-end test. Let's look at
|
||||
the unit test first.
|
||||
|
||||
__`test/unit/controllerSpec.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
var scope, ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = {},
|
||||
ctrl = new PhoneListCtrl(scope);
|
||||
});
|
||||
|
||||
|
||||
it('should create "phones" model with 3 phones', function() {
|
||||
expect(scope.phones.length).toBe(3);
|
||||
});
|
||||
|
||||
|
||||
it('should set the default value of orderProp model', function() {
|
||||
expect(scope.orderProp).toBe('age');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
The unit test now verifies that the default ordering property is set.
|
||||
|
||||
We used Jasmine's API to extract the controller construction into a `beforeEach` block, which is
|
||||
shared by all tests in the parent `describe` block.
|
||||
|
||||
To run the unit tests, once again execute the `./scripts/test.sh` script and you should see the
|
||||
following output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
..
|
||||
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
Let's turn our attention to the end-to-end test.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
...
|
||||
it('should be possible to control phone order via the drop down select box',
|
||||
function() {
|
||||
//let's narrow the dataset to make the test assertions shorter
|
||||
input('query').enter('tablet');
|
||||
|
||||
expect(repeater('.phones li', 'Phone List').column('phone.name')).
|
||||
toEqual(["Motorola XOOM\u2122 with Wi-Fi",
|
||||
"MOTOROLA XOOM\u2122"]);
|
||||
|
||||
select('orderProp').option('Alphabetical');
|
||||
|
||||
expect(repeater('.phones li', 'Phone List').column('phone.name')).
|
||||
toEqual(["MOTOROLA XOOM\u2122",
|
||||
"Motorola XOOM\u2122 with Wi-Fi"]);
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
|
||||
|
||||
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}.
|
||||
|
||||
# Experiments
|
||||
|
||||
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
|
||||
you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the
|
||||
ordering will default to unordered/natural order.
|
||||
|
||||
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
|
||||
text.
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,238 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 5 - XHRs & Dependency Injection
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="5"></ul>
|
||||
|
||||
|
||||
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
|
||||
from our server using one of angular's built-in {@link api/angular.module.ng services} called {@link
|
||||
api/angular.module.ng.$http $http}. We will use angular's {@link guide/dev_guide.di dependency
|
||||
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="5"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
You should now see a list of 20 phones.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-4...step-5
|
||||
GitHub}:
|
||||
|
||||
## Data
|
||||
|
||||
The `app/phones/phone.json` file in your project is a dataset that contains a larger list of phones
|
||||
stored in the JSON format.
|
||||
|
||||
Following is a sample of the file:
|
||||
<pre>
|
||||
[
|
||||
{
|
||||
"age": 13,
|
||||
"id": "motorola-defy-with-motoblur",
|
||||
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
|
||||
"snippet": "Are you ready for everything life throws your way?"
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
</pre>
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We'll use angular's {@link api/angular.module.ng.$http $http} service in our controller to make an HTTP
|
||||
request to your web server to fetch the data in the `app/phones/phones.json` file. `$http` is just
|
||||
one of several built-in {@link api/angular.module.ng angular services} that handle common operations
|
||||
in web apps. Angular injects these services for you where you need them.
|
||||
|
||||
Services are managed by angular's {@link guide/dev_guide.di DI subsystem}. Dependency injection
|
||||
helps to make your web apps both well-structured (e.g., separate components for presentation, data,
|
||||
and control) and loosely coupled (dependencies between components are not resolved by the
|
||||
components themselves, but by the DI subsystem).
|
||||
|
||||
__`app/js/controllers.js:`__
|
||||
<pre>
|
||||
function PhoneListCtrl($scope, $http) {
|
||||
$http.get('phones/phones.json').success(function(data) {
|
||||
$scope.phones = data;
|
||||
});
|
||||
|
||||
$scope.orderProp = 'age';
|
||||
}
|
||||
|
||||
//PhoneListCtrl.$inject = ['$scope', '$http'];
|
||||
</pre>
|
||||
|
||||
`$http` makes an HTTP GET request to our web server, asking for `phone/phones.json` (the url is
|
||||
relative to our `index.html` file). The server responds by providing the data in the json file.
|
||||
(The response might just as well have been dynamically generated by a backend server. To the
|
||||
browser and our app they both look the same. For the sake of simplicity we used a json file in this
|
||||
tutorial.)
|
||||
|
||||
The `$http` service returns a {@link api/angular.module.ng.$q promise object} with a `success`
|
||||
method. We call this method to handle the asynchronous response and assign the phone data to the
|
||||
scope controlled by this controller, as a model called `phones`. Notice that angular detected the
|
||||
json response and parsed it for us!
|
||||
|
||||
To use a service in angular, you simply declare the names of the dependencies you need as arguments
|
||||
to the controller's constructor function, as follows:
|
||||
|
||||
function PhoneListCtrl($scope, $http) {...}
|
||||
|
||||
Angular's dependency injector provides services to your controller when the controller is being
|
||||
constructed. The dependency injector also takes care of creating any transitive dependencies the
|
||||
service may have (services often depend upon other services).
|
||||
|
||||
Note that the names of arguments are significant, because the injector uses these to look up the
|
||||
dependencies.
|
||||
|
||||
|
||||
<img src="img/tutorial/xhr_service_final.png">
|
||||
|
||||
|
||||
### '$' Prefix Naming Convention
|
||||
|
||||
You can create your own services, and in fact we will do exactly that in step 11. As a naming
|
||||
convention, angular's built-in services, Scope methods and a few other angular APIs have a '$'
|
||||
prefix in front of the name. Don't use a '$' prefix when naming your services and models, in order
|
||||
to avoid any possible naming collisions.
|
||||
|
||||
### A Note on Minification
|
||||
|
||||
Since angular infers the controller's dependencies from the names of arguments to the controller's
|
||||
constructor function, if you were to {@link http://en.wikipedia.org/wiki/Minification_(programming)
|
||||
minify} the JavaScript code for `PhoneListCtrl` controller, all of its function arguments would be
|
||||
minified as well, and the dependency injector would not being able to identify services correctly.
|
||||
|
||||
To overcome issues caused by minification, just assign an array with service identifier strings
|
||||
into the `$inject` property of the controller function, just like the last line in the snippet
|
||||
(commented out) suggests:
|
||||
|
||||
PhoneListCtrl.$inject = ['$scope', '$http'];
|
||||
|
||||
There is also one more way to specify this dependency list and avoid minification issues — using the
|
||||
bracket notation which wraps the function to be injected into an array of strings (representing the
|
||||
dependency names) followed by the function to be injected:
|
||||
|
||||
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];
|
||||
|
||||
Both of these methods work with any function that can be injected by Angular, so it's up to your
|
||||
project's style guide to decide which one you use.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
|
||||
Because we started using dependency injection and our controller has dependencies, constructing the
|
||||
controller in our tests is a bit more complicated. We could use the `new` operator and provide the
|
||||
constructor with some kind of fake `$http` implementation. However, the recommended (and easier) way
|
||||
is to create a controller in the test environment in the same way that angular does it in the
|
||||
production code behind the scenes, as follows:
|
||||
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
var scope, ctrl, $httpBackend;
|
||||
|
||||
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.expectGET('phones/phones.json').
|
||||
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
|
||||
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller(PhoneListCtrl, {$scope: scope});
|
||||
}));
|
||||
</pre>
|
||||
|
||||
Note: Because we loaded Jasmine and `angular-mocks.js` in our test environment, we got two helper
|
||||
methods {@link api/angular.mock.module module} and {@link api/angular.mock.inject inject} that we'll
|
||||
use to access and configure the injector.
|
||||
|
||||
We created the controller in the test environment, as follows:
|
||||
|
||||
* We used the `inject` helper method to inject instances of
|
||||
{@link api/angular.module.ng.$rootScope $rootScope},
|
||||
{@link api/angular.module.ng.$controller $controller} and
|
||||
{@link api/angular.module.ng.$httpBackend $httpBackend} services into the Jasmine's `beforeEach`
|
||||
function. These instances come from an injector which is recreated from scratch for every single
|
||||
test. This guarantees that each test starts from a well known starting point and each test is
|
||||
isolated from the work done in other tests.
|
||||
|
||||
* We created a new scope for our controller by calling `$rootScope.$new()`
|
||||
|
||||
* We called `scope.$new(PhoneListCtrl)` to get Angular to create the child scope associated with
|
||||
the `PhoneListCtrl` controller.
|
||||
|
||||
Because our code now uses the `$http` service to fetch the phone list data in our controller, before
|
||||
we create the `PhoneListCtrl` child scope, we need to tell the testing harness to expect an
|
||||
incoming request from the controller. To do this we:
|
||||
|
||||
* Request `$httpBackend` service to be injected into our `beforeEach` function. This is a mock
|
||||
mock version of the service that in production environment facilitates all XHR and JSONP requests.
|
||||
The mock version of this service allows you to write tests without having to deal with
|
||||
native APIs and the global state associated with them — both of which make testing a nightmare.
|
||||
|
||||
* Use the `$httpBackend.expectGET` method to train the `$httpBackend` service to expect an incoming
|
||||
HTTP request and tell it what to respond with. Note that the responses are not returned until we call
|
||||
the `$httpBackend.flush` method.
|
||||
|
||||
Now, we will make assertions to verify that the `phones` model doesn't exist on the scope, before
|
||||
the response is received:
|
||||
|
||||
<pre>
|
||||
it('should create "phones" model with 2 phones fetched from xhr', function() {
|
||||
expect(scope.phones).toBeUndefined();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(scope.phones).toEqual([{name: 'Nexus S'},
|
||||
{name: 'Motorola DROID'}]);
|
||||
});
|
||||
</pre>
|
||||
|
||||
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
|
||||
promise returned by the `$http` service to be resolved with the trained response.
|
||||
|
||||
* We make the assertions, verifying that the phone model now exists on the scope.
|
||||
|
||||
Finally, we verify that the default value of `orderProp` is set correctly:
|
||||
|
||||
<pre>
|
||||
it('should set the default value of orderProp model', function() {
|
||||
expect(scope.orderProp).toBe('age');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
..
|
||||
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* At the bottom of `index.html`, add a `{{phones | json}}` binding to see the list of phones
|
||||
displayed in json format.
|
||||
|
||||
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
|
||||
to the first 5 in the list. Use the following code in the $http callback:
|
||||
|
||||
$scope.phones = data.splice(0, 5);
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
Now that you have learned how easy it is to use angular services (thanks to Angular's 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>
|
||||
@@ -0,0 +1,106 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 6 - Templating Links & Images
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="6"></ul>
|
||||
|
||||
|
||||
In this step, you will add thumbnail images for the phones in the phone list, and links that, for
|
||||
now, will go nowhere. In subsequent steps you will use the links to display additional information
|
||||
about the phones in the catalog.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="6"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
You should now see links and images of the phones in the list.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-5...step-6
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Data
|
||||
|
||||
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
|
||||
urls point to the `app/img/phones/` directory.
|
||||
|
||||
__`app/phones/phones.json`__ (sample snippet):
|
||||
<pre>
|
||||
[
|
||||
{
|
||||
...
|
||||
"id": "motorola-defy-with-motoblur",
|
||||
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
|
||||
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
</pre>
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
To dynamically generate links that will in the future lead to phone detail pages, we used the
|
||||
now-familiar double-curly brace binding 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.
|
||||
|
||||
We also added phone images next to each record using an image tag with the {@link
|
||||
api/angular.module.ng.$compileProvider.directive.ngSrc ngSrc} directive. That directive prevents the
|
||||
browser from treating the angular `{{ expression }}` markup literally, and initiating a request to
|
||||
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
|
||||
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
|
||||
Using `ngSrc` (`ng-src`) prevents the browser from making an http request to an invalid location.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
__`test/e2e/scenarios.js`__:
|
||||
<pre>
|
||||
...
|
||||
it('should render phone specific links', function() {
|
||||
input('query').enter('nexus');
|
||||
element('.phones li a').click();
|
||||
expect(browser().location().url()).toBe('/phones/nexus-s');
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
We added a new end-to-end test to verify that the app is generating correct links to the phone
|
||||
views that we will implement in the upcoming steps.
|
||||
|
||||
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-6/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Replace the `ng-src` directive with a plain old `src` attribute. Using tools such as Firebug,
|
||||
or Chrome's Web Inspector, or inspecting the webserver access logs, confirm that the app is indeed
|
||||
making an extraneous request to `/app/%7B%7Bphone.imageUrl%7D%7D` (or
|
||||
`/app/{{phone.imageUrl}}`).
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,261 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 7 - Routing & Multiple Views
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="7"></ul>
|
||||
|
||||
|
||||
In this step, you will learn how to create a layout template and how to build an app that has
|
||||
multiple views by adding routing.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="7"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
Note that when you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
|
||||
and the same phone list appears in the browser. When you click on a phone link the stub of a phone
|
||||
detail page is displayed.
|
||||
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-6...step-7
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Multiple Views, Routing and Layout Template
|
||||
|
||||
Our app is slowly growing and becoming more complex. Before step 7, the app provided our users with
|
||||
a single view (the list of all phones), and all of the template code was located in the
|
||||
`index.html` file. The next step in building the app is to add a view that will show detailed
|
||||
information about each of the devices in our list.
|
||||
|
||||
To add the detailed view, we could expand the `index.html` file to contain template code for both
|
||||
views, but that would get messy very quickly. Instead, we are going to turn the `index.html`
|
||||
template into what we call a "layout template". This is a template that is common for all views in
|
||||
our application. Other "partial templates" are then included into this layout template depending on
|
||||
the current "route" — the view that is currently displayed to the user.
|
||||
|
||||
Application routes in angular are declared via the
|
||||
{@link api/angular.module.ng.$routeProvider $routeProvider}, which is the provider of the
|
||||
{@link api/angular.module.ng.$route $route service}. This service makes it easy to wire together
|
||||
controllers, view templates, and the current
|
||||
URL location in the browser. Using this feature we can implement {@link
|
||||
http://en.wikipedia.org/wiki/Deep_linking deep linking}, which lets us utilize the browser's
|
||||
history (back and forward navigation) and bookmarks.
|
||||
|
||||
|
||||
### A Note About DI, Injector and Providers
|
||||
|
||||
As you noticed the dependency injection is the core feature of AngularJS, so it's important for you
|
||||
to understand a thing or two about how it works.
|
||||
|
||||
When the application bootstraps, Angular creates an injector that will be used for all DI stuff in
|
||||
this app. The injector itself doesn't know anything about what `$http` or `$route` services do, in
|
||||
fact it doesn't even know about the existence of these services unless it is configured with proper
|
||||
module definitions. The sole responsibilities of the injector are to load specified module
|
||||
definition(s), register all service providers defined in these modules and when asked inject
|
||||
a specified function with dependencies (services) that it lazily instantiates via their providers.
|
||||
|
||||
Providers are objects that provide (create) instances of services and expose configuration apis
|
||||
that can be used to control the creation and runtime behavior of a service. In case of the `$route`
|
||||
service, the `$routeProvider` exposes apis that allow you to define routes for your application.
|
||||
|
||||
Angular modules solve the problem of removing global state from the application and provide a way
|
||||
of configuring the injector. As opposed to AMD or require.js modules, Angular modules don't try to
|
||||
solve the problem of script load ordering or lazy script fetching. These goals are orthogonal and
|
||||
both module systems can live side by side and fulfil their goals.
|
||||
|
||||
## The App Module
|
||||
|
||||
__`app/js/app.js`:__
|
||||
<pre>
|
||||
angular.module('phonecat', []).
|
||||
config(['$routeProvider', function($routeProvider) {
|
||||
$routeProvider.
|
||||
when('/phones', {template: 'partials/phone-list.html', controller: PhoneListCtrl}).
|
||||
when('/phones/:phoneId', {template: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
|
||||
otherwise({redirectTo: '/phones'});
|
||||
}]);
|
||||
</pre>
|
||||
|
||||
In order to configure our application with routes, we need to create a module for our application.
|
||||
We call this module `phonecatApp` and using the `config` api we request the `$routeProvider` to be
|
||||
injected into our config function and use `$routeProvider.when` api to define our routes.
|
||||
|
||||
Note that during the injector configuration phase, the providers can be injected as well, but they
|
||||
will not be available for injection once the injector is created and starts creating service
|
||||
instances.
|
||||
|
||||
Our application routes were defined as follows:
|
||||
|
||||
* 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
|
||||
`phone-detail.html` template and the `PhoneDetailCtrl` controller.
|
||||
|
||||
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
|
||||
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
|
||||
|
||||
The statement `$route.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when
|
||||
the browser address doesn't match either of our routes.
|
||||
|
||||
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
|
||||
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
|
||||
URL. All variables defined with the `:` notation are extracted into the
|
||||
{@link api/angular.module.ng.$routeParams $routeParams} object.
|
||||
|
||||
|
||||
In order for our application to bootstrap with our newly created module we'll also need to specify
|
||||
the module name as the value of the {@link api/angular.module.ng.$compileProvider.directive.ngApp ngApp}
|
||||
directive:
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html ng-app="phonecat">
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
## Controllers
|
||||
|
||||
__`app/js/controller.js`:__
|
||||
<pre>
|
||||
...
|
||||
function PhoneDetailCtrl($scope, $routeParams) {
|
||||
$scope.phoneId = $routeParams.phoneId;
|
||||
}
|
||||
|
||||
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams'];
|
||||
</pre>
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
The `$route` service is usually used in conjunction with the {@link api/angular.module.ng.$compileProvider.directive.ngView
|
||||
ngView} directive. The role of the `ngView` directive is to include the view template for the current
|
||||
route into the layout template, which makes it a perfect fit for our `index.html` template.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
<html ng-app="phonecat">
|
||||
<head>
|
||||
...
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div ng-view></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
Note that we removed most of the code in the `index.html` template and replaced it with a single
|
||||
line containing a div with `ng-view` attribute. The code that we removed was placed into the
|
||||
`phone-list.html` template:
|
||||
|
||||
__`app/partials/phone-list.html`:__
|
||||
<pre>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span2">
|
||||
<!--Sidebar content-->
|
||||
|
||||
Search: <input ng-model="query">
|
||||
Sort by:
|
||||
<select ng-model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<div class="span10">
|
||||
<!--Body content-->
|
||||
|
||||
<ul class="phones">
|
||||
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
|
||||
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
|
||||
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
|
||||
<p>{{phone.snippet}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
<div style="display:none">
|
||||
TODO!
|
||||
<img src="img/tutorial/tutorial_07_final.png">
|
||||
</div>
|
||||
|
||||
We also added a placeholder template for the phone details view:
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
TBD: detail view for {{phoneId}}
|
||||
</pre>
|
||||
|
||||
Note how we are using `phoneId` model defined in the `PhoneDetailCtrl` controller.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
To automatically verify that everything is wired properly, we wrote end-to-end tests that navigate
|
||||
to various URLs and verify that the correct view was rendered.
|
||||
|
||||
<pre>
|
||||
...
|
||||
it('should redirect index.html to index.html#/phones', function() {
|
||||
browser().navigateTo('../../app/index.html');
|
||||
expect(browser().location().url()).toBe('/phones');
|
||||
});
|
||||
...
|
||||
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html#/phones/nexus-s');
|
||||
});
|
||||
|
||||
|
||||
it('should display placeholder page with phoneId', function() {
|
||||
expect(binding('phoneId')).toBe('nexus-s');
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
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-7/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Try to add an `{{orderProp}}` binding to `index.html`, and you'll see that nothing happens even
|
||||
when you are in the phone list view. This is because the `orderProp` model is visible only in the
|
||||
scope managed by `PhoneListCtrl`, which is associated with the `<div ng-view>` element. If you add
|
||||
the same binding into the `phone-list.html` template, the binding will work as expected.
|
||||
|
||||
<div style="display: none">
|
||||
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
|
||||
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
|
||||
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
|
||||
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
|
||||
inheritance and model property shadowing do some wonders.
|
||||
</div>
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,197 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 8 - More Templating
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="8"></ul>
|
||||
|
||||
|
||||
In this step, you will implement the phone details view, which is displayed when a user clicks on a
|
||||
phone in the phone list.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="8"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
Now when you click on a phone on the list, the phone details page with phone-specific information
|
||||
is displayed.
|
||||
|
||||
To implement the phone details view we will use {@link api/angular.module.ng.$http $http} to fetch
|
||||
our data, and we'll flesh out the `phone-details.html` view template.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-7...step-8
|
||||
GitHub}:
|
||||
|
||||
## Data
|
||||
|
||||
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
|
||||
phone:
|
||||
|
||||
__`app/phones/nexus-s.json`:__ (sample snippet)
|
||||
<pre>
|
||||
{
|
||||
"additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
|
||||
"android": {
|
||||
"os": "Android 2.3",
|
||||
"ui": "Android"
|
||||
},
|
||||
...
|
||||
"images": [
|
||||
"img/phones/nexus-s.0.jpg",
|
||||
"img/phones/nexus-s.1.jpg",
|
||||
"img/phones/nexus-s.2.jpg",
|
||||
"img/phones/nexus-s.3.jpg"
|
||||
],
|
||||
"storage": {
|
||||
"flash": "16384MB",
|
||||
"ram": "512MB"
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
|
||||
Each of these files describes various properties of the phone using the same data structure. We'll
|
||||
show this data in the phone detail view.
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the json files. This works
|
||||
the same way as the phone list controller.
|
||||
|
||||
__`app/js/controller.js`:__
|
||||
<pre>
|
||||
function PhoneDetailCtrl($scope, $routeParams, $http) {
|
||||
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
|
||||
$scope.phone = data;
|
||||
});
|
||||
}
|
||||
|
||||
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
|
||||
</pre>
|
||||
|
||||
To construct the URL for the HTTP request, we use `$routeParams.phoneId` extracted from the current
|
||||
route by the `$route` service.
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
The TBD placeholder line has been replaced with lists and bindings that comprise the phone details.
|
||||
Note where we use the angular `{{expression}}` markup and `ngRepeater`s to project phone data from
|
||||
our model into the view.
|
||||
|
||||
|
||||
__`app/partials/phone-details.html`:__
|
||||
<pre>
|
||||
<img ng-src="{{phone.images[0]}}" class="phone">
|
||||
|
||||
<h1>{{phone.name}}</h1>
|
||||
|
||||
<p>{{phone.description}}</p>
|
||||
|
||||
<ul class="phone-thumbs">
|
||||
<li ng-repeat="img in phone.images">
|
||||
<img ng-src="{{img}}">
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="specs">
|
||||
<li>
|
||||
<span>Availability and Networks</span>
|
||||
<dl>
|
||||
<dt>Availability</dt>
|
||||
<dd ng-repeat="availability in phone.availability">{{availability}}</dd>
|
||||
</dl>
|
||||
</li>
|
||||
...
|
||||
</li>
|
||||
<span>Additional Features</span>
|
||||
<dd>{{phone.additionalFeatures}}</dd>
|
||||
</li>
|
||||
</ul>
|
||||
</pre>
|
||||
|
||||
<div style="display: none">
|
||||
TODO!
|
||||
<img src="img/tutorial/tutorial_08-09_final.png">
|
||||
</div>
|
||||
|
||||
## Test
|
||||
|
||||
We wrote a new unit test that is similar to the one we wrote for the `PhoneListCtrl` controller in
|
||||
step 5.
|
||||
|
||||
__`test/unit/controllerSpec.js`:__
|
||||
<pre>
|
||||
...
|
||||
describe('PhoneDetailCtrl', function(){
|
||||
var scope, $httpBackend, ctrl;
|
||||
|
||||
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});
|
||||
|
||||
$routeParams.phoneId = 'xyz';
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
|
||||
}));
|
||||
|
||||
|
||||
it('should fetch phone detail', function() {
|
||||
expect(scope.phone).toBeUndefined();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(scope.phone).toEqual({name:'phone xyz'});
|
||||
});
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
...
|
||||
Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms)
|
||||
Chrome 19.0.1084.36 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
|
||||
|
||||
|
||||
We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the
|
||||
heading on the page is "Nexus S".
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
...
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
browser().navigateTo('../../app/index.html#/phones/nexus-s');
|
||||
});
|
||||
|
||||
|
||||
it('should display nexus-s page', function() {
|
||||
expect(binding('phone.name')).toBe('Nexus S');
|
||||
});
|
||||
});
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
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-8/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Using the {@link guide/dev_guide.e2e-testing Angular's end-to-end test runner API}, write a test
|
||||
that verifies that we display 4 thumbnail images on the Nexus S details page.
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,143 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 9 - Filters
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="9"></ul>
|
||||
|
||||
|
||||
In this step you will learn how to create your own custom display filter.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="9"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
Navigate to one of the detail pages.
|
||||
|
||||
In the previous step, the details page displayed either "true" or "false" to indicate whether
|
||||
certain phone features were present or not. We have used a custom filter to convert those text
|
||||
strings into glyphs: ✓ for "true", and ✘ for "false". Let's see, what the filter code looks like.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-8...step-9
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Custom Filter
|
||||
|
||||
In order to create a new filter, you are going to create a `phonecatFilters` module and register
|
||||
your custom filter with this module:
|
||||
|
||||
__`app/js/filters.js`:__
|
||||
<pre>
|
||||
angular.module('phonecatFilters', []).filter('checkmark', function() {
|
||||
return function(input) {
|
||||
return input ? '\u2713' : '\u2718';
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we
|
||||
return one of two unicode characters we have chosen to represent true or false (`\u2713` and
|
||||
`\u2718`).
|
||||
|
||||
Now that our filter is ready, we need to register the `phonecatFilters` module as a dependency for
|
||||
our main `phonecat` module.
|
||||
|
||||
__`app/js/app.js`:__
|
||||
<pre>
|
||||
...
|
||||
angular.module('phonecat', ['phonecatFilters']).
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
Since the filter code lives in the `app/js/filters.js` file, we need to include this file in our
|
||||
layout template.
|
||||
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
<script src="js/controllers.js"></script>
|
||||
<script src="js/filters.js"></script>
|
||||
...
|
||||
</pre>
|
||||
|
||||
The syntax for using filters in angular templates is as follows:
|
||||
|
||||
{{ expression | filter }}
|
||||
|
||||
Let's employ the filter in the phone details template:
|
||||
|
||||
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
...
|
||||
<dl>
|
||||
<dt>Infrared</dt>
|
||||
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
|
||||
<dt>GPS</dt>
|
||||
<dd>{{phone.connectivity.gps | checkmark}}</dd>
|
||||
</dl>
|
||||
...
|
||||
</pre>
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
Filters, like any other component, should be tested and these tests are very easy to write.
|
||||
|
||||
__`test/unit/filtersSpec.js`:__
|
||||
<pre>
|
||||
describe('filter', function() {
|
||||
|
||||
beforeEach(module('phonecatFilters'));
|
||||
|
||||
|
||||
describe('checkmark', function() {
|
||||
|
||||
it('should convert boolean values to unicode checkmark or cross',
|
||||
inject(function(checkmarkFilter) {
|
||||
expect(checkmarkFilter(true)).toBe('\u2713');
|
||||
expect(checkmarkFilter(false)).toBe('\u2718');
|
||||
}));
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
Note that you need to configure our test injector with the `phonecatFilters` module before any of
|
||||
our filter tests execute.
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
....
|
||||
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
# Experiments
|
||||
|
||||
* Let's experiment with some of the {@link api/angular.module.ng.$filter built-in angular filters} and add the
|
||||
following bindings to `index.html`:
|
||||
* `{{ "lower cap string" | uppercase }}`
|
||||
* `{{ {foo: "bar", baz: 23} | json }}`
|
||||
* `{{ 1304375948024 | date }}`
|
||||
* `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}`
|
||||
|
||||
* We can also create a model with an input element, and combine it with a filtered binding. Add
|
||||
the following to index.html:
|
||||
|
||||
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,141 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 10 - Event Handlers
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="10"></ul>
|
||||
|
||||
|
||||
In this step, you will add a clickable phone image swapper to the phone details page.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="10"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
The phone details view displays one large image of the current phone and several smaller thumbnail
|
||||
images. It would be great if we could replace the large image with any of the thumbnails just by
|
||||
clicking on the desired thumbnail image. Let's have a look at how we can do this with angular.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-9...step-10
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
...
|
||||
function PhoneDetailCtrl($scope, $routeParams, $http) {
|
||||
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
|
||||
$scope.phone = data;
|
||||
$scope.mainImageUrl = data.images[0];
|
||||
});
|
||||
|
||||
$scope.setImage = function(imageUrl) {
|
||||
$scope.mainImageUrl = imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
|
||||
</pre>
|
||||
|
||||
In the `PhoneDetailCtrl` controller, we created the `mainImageUrl` model property and set its
|
||||
default value to the first phone image url.
|
||||
|
||||
We also created a `setImage` event handler function that will change the value of `mainImageUrl`.
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
__`app/partials/phone-detail.html`:__
|
||||
<pre>
|
||||
<img ng-src="{{mainImageUrl}}" class="phone">
|
||||
|
||||
...
|
||||
|
||||
<ul class="phone-thumbs">
|
||||
<li ng-repeat="img in phone.images">
|
||||
<img ng-src="{{img}}" ng-click="setImage(img)">
|
||||
</li>
|
||||
</ul>
|
||||
...
|
||||
</pre>
|
||||
|
||||
We bound the `ngSrc` directive of the large image to the `mainImageUrl` property.
|
||||
|
||||
We also registered an {@link api/angular.module.ng.$compileProvider.directive.ngClick `ngClick`}
|
||||
handler with thumbnail images. When a user clicks on one of the thumbnail images, the handler will
|
||||
use the `setImage` event handler function to change the value of the `mainImageUrl` property to the
|
||||
url of the thumbnail image.
|
||||
|
||||
<div style="display: none">
|
||||
TODO!
|
||||
<img src="img/tutorial/tutorial_10-11_final.png">
|
||||
</div>
|
||||
|
||||
## Test
|
||||
|
||||
To verify this new feature, we added two end-to-end tests. One verifies that the main image is set
|
||||
to the first phone image by default. The second test clicks on several thumbnail images and
|
||||
verifies that the main image changed appropriately.
|
||||
|
||||
__`test/e2e/scenarios.js`:__
|
||||
<pre>
|
||||
...
|
||||
describe('Phone detail view', function() {
|
||||
|
||||
...
|
||||
|
||||
it('should display the first phone image as the main phone image', function() {
|
||||
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
|
||||
});
|
||||
|
||||
|
||||
it('should swap main image if a thumbnail image is clicked on', function() {
|
||||
element('.phone-thumbs li:nth-child(3) img').click();
|
||||
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
|
||||
|
||||
element('.phone-thumbs li:nth-child(1) img').click();
|
||||
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
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-8/test/e2e/runner.html
|
||||
angular's server}.
|
||||
|
||||
# Experiments
|
||||
|
||||
* Let's add a new controller method to `PhoneDetailCtrl`:
|
||||
|
||||
$scope.hello = function(name) {
|
||||
alert('Hello ' + (name || 'world') + '!');
|
||||
}
|
||||
|
||||
and add:
|
||||
|
||||
<button ng-click="hello('Elmo')">Hello</button>
|
||||
|
||||
to the `phone-details.html` template.
|
||||
|
||||
<div style="display: none">
|
||||
TODO!
|
||||
The controller methods are inherited between controllers/scopes, so you can use the same snippet
|
||||
in the `phone-list.html` template as well.
|
||||
|
||||
* Move the `hello` method from `PhoneCatCtrl` to `PhoneListCtrl` and you'll see that the button
|
||||
declared in `index.html` will stop working, while the one declared in the `phone-list.html`
|
||||
template remains operational.
|
||||
</div>
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,223 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: 11 - REST and Custom Services
|
||||
@description
|
||||
|
||||
<ul doc:tutorial-nav="11"></ul>
|
||||
|
||||
|
||||
In this step, you will improve the way our app fetches data.
|
||||
|
||||
|
||||
<doc:tutorial-instructions step="11"></doc:tutorial-instructions>
|
||||
|
||||
|
||||
The last improvement we will make to our app is to define a custom service that represents a {@link
|
||||
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this client we
|
||||
can make xhr requests for data in an easier way, without having to deal with the lower-level {@link
|
||||
api/angular.module.ng.$http $http} API, HTTP methods and URLs.
|
||||
|
||||
The most important changes are listed below. You can see the full diff on {@link
|
||||
https://github.com/angular/angular-phonecat/compare/step-10...step-11
|
||||
GitHub}:
|
||||
|
||||
|
||||
## Template
|
||||
|
||||
The custom service is defined in `app/js/services.js` so we need to include this file in our layout
|
||||
template. Additionally, we also need to load the `angular-resource.js` file, which contains the
|
||||
`ngResource` module and in it the `$resource` service, that we'll soon use:
|
||||
|
||||
__`app/index.html`.__
|
||||
<pre>
|
||||
...
|
||||
<script src="js/services.js"></script>
|
||||
<script src="lib/angular/angular-resource.js"></script>
|
||||
...
|
||||
</pre>
|
||||
|
||||
## Service
|
||||
|
||||
__`app/js/services.js`.__
|
||||
<pre>
|
||||
angular.module('phonecatServices', ['ngResource']).
|
||||
factory('Phone', function($resource){
|
||||
return $resource('phones/:phoneId.json', {}, {
|
||||
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
We used the module API to register a custom service using a factory function. We passed in the name
|
||||
of the service - 'Phone' - and the factory function. The factory function is similar to a
|
||||
controller's constructor in that both can declare dependencies via function arguments. The Phone
|
||||
service declared a dependency on the `$resource` service.
|
||||
|
||||
The {@link api/angular.module.ngResource.$resource `$resource`} service makes it easy to create a
|
||||
{@link http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client with just a few
|
||||
lines of code. This client can then be used in our application, instead of the lower-level {@link
|
||||
api/angular.module.ng.$http $http} service.
|
||||
|
||||
|
||||
## Controller
|
||||
|
||||
We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the
|
||||
lower-level {@link api/angular.module.ng.$http $http} service, replacing it with a new service called
|
||||
`Phone`. Angular's {@link api/angular.module.ngResource.$resource `$resource`} service is easier to
|
||||
use than `$http for interacting with data sources exposed as RESTful resources. It is also easier
|
||||
now to understand what the code in our controllers is doing.
|
||||
|
||||
__`app/js/controllers.js`.__
|
||||
<pre>
|
||||
...
|
||||
|
||||
function PhoneListCtrl($scope, Phone) {
|
||||
$scope.phones = Phone.query();
|
||||
$scope.orderProp = 'age';
|
||||
}
|
||||
|
||||
//PhoneListCtrl.$inject = ['$scope', 'Phone'];
|
||||
|
||||
|
||||
|
||||
function PhoneDetailCtrl($scope, $routeParams, Phone) {
|
||||
$scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
|
||||
$scope.mainImageUrl = phone.images[0];
|
||||
});
|
||||
|
||||
$scope.setImage = function(imageUrl) {
|
||||
$scope.mainImageUrl = imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', 'Phone'];
|
||||
</pre>
|
||||
|
||||
Notice how in `PhoneListCtrl` we replaced:
|
||||
|
||||
$http.get('phones/phones.json').success(function(data) {
|
||||
$scope.phones = data;
|
||||
});
|
||||
|
||||
with:
|
||||
|
||||
$scope.phones = Phone.query();
|
||||
|
||||
This is a simple statement that we want to query for all phones.
|
||||
|
||||
An important thing to notice in the code above is that we don't pass any callback functions when
|
||||
invoking methods of our Phone service. Although it looks as if the result were returned
|
||||
synchronously, that is not the case at all. What is returned synchronously is a "future" — an
|
||||
object, which will be filled with data when the xhr response returns. Because of the data-binding
|
||||
in angular, we can use this future and bind it to our template. Then, when the data arrives, the
|
||||
view will automatically update.
|
||||
|
||||
Sometimes, relying on the future object and data-binding alone is not sufficient to do everything
|
||||
we require, so in these cases, we can add a callback to process the server response. The
|
||||
`PhoneDetailCtrl` controller illustrates this by setting the `mainImageUrl` in a callback.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
We have modified our unit tests to verify that our new service is issuing HTTP requests and
|
||||
processing them as expected. The tests also check that our controllers are interacting with the
|
||||
service correctly.
|
||||
|
||||
The {@link api/angular.module.ngResource.$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
|
||||
http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html Jasmine matcher}. When the
|
||||
`toEqualData` matcher compares two objects, it takes only object properties into account and
|
||||
ignores methods.
|
||||
|
||||
|
||||
__`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
beforeEach(function(){
|
||||
this.addMatchers({
|
||||
toEqualData: function(expected) {
|
||||
return angular.equals(this.actual, expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
beforeEach(module('phonecatServices'));
|
||||
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
var scope, ctrl, $httpBackend;
|
||||
|
||||
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.expectGET('phones/phones.json').
|
||||
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
|
||||
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller(PhoneListCtrl, {$scope: scope});
|
||||
}));
|
||||
|
||||
|
||||
it('should create "phones" model with 2 phones fetched from xhr', function() {
|
||||
expect(scope.phones).toEqual([]);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(scope.phones).toEqualData(
|
||||
[{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
|
||||
});
|
||||
|
||||
|
||||
it('should set the default value of orderProp model', function() {
|
||||
expect(scope.orderProp).toBe('age');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('PhoneDetailCtrl', function(){
|
||||
var scope, $httpBackend, ctrl,
|
||||
xyzPhoneData = function() {
|
||||
return {
|
||||
name: 'phone xyz',
|
||||
images: ['image/url1.png', 'image/url2.png']
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
|
||||
|
||||
$routeParams.phoneId = 'xyz';
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
|
||||
}));
|
||||
|
||||
|
||||
it('should fetch phone detail', function() {
|
||||
expect(scope.phone).toEqualData({});
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(scope.phone).toEqualData(xyzPhoneData());
|
||||
});
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
|
||||
output.
|
||||
|
||||
Chrome: Runner reset.
|
||||
....
|
||||
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
|
||||
Chrome 19.0.1084.36 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,21 @@
|
||||
@ngdoc overview
|
||||
@name Tutorial: The End
|
||||
@description
|
||||
|
||||
Our application is now complete. Feel free to experiment with the code further, and jump back to
|
||||
previous steps using the `git checkout` or `goto_step.sh` commands.
|
||||
|
||||
For more details and examples of the angular concepts we touched on in this tutorial, see the
|
||||
{@link guide/ Developer Guide}.
|
||||
|
||||
For several more examples of code, see the {@link cookbook/ Cookbook}.
|
||||
|
||||
When you are ready to start developing a project using angular, we recommend that you bootstrap
|
||||
your development with the {@link https://github.com/angular/angular-seed angular seed} project.
|
||||
|
||||
We hope this tutorial was useful to you and that you learned enough about angular to make you want
|
||||
to learn more. We especially hope you are inspired to go out and develop angular web apps of your
|
||||
own, and that you might be interested in {@link misc/contribute contributing} to angular.
|
||||
|
||||
If you have questions or feedback or just want to say "hi", please post a message at {@link
|
||||
https://groups.google.com/forum/#!forum/angular}.
|
||||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 124 KiB |
@@ -35,7 +35,7 @@ describe('ngdoc', function() {
|
||||
var d2 = new Doc('@name a.b.ng-c').parse();
|
||||
var d3 = new Doc('@name some text: more text').parse();
|
||||
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
|
||||
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng:c');
|
||||
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng-c');
|
||||
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
|
||||
});
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ function writeTheRest(writesFuture) {
|
||||
writesFuture.push(writer.copyTpl('offline.html'));
|
||||
writesFuture.push(writer.copyTpl('docs-scenario.html'));
|
||||
writesFuture.push(writer.copyTpl('jquery.min.js'));
|
||||
writesFuture.push(writer.copyTpl('jquery.js'));
|
||||
|
||||
writesFuture.push(writer.output('docs-keywords.js',
|
||||
['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';']));
|
||||
|
||||
@@ -372,10 +372,10 @@ Doc.prototype = {
|
||||
dom.text(')');
|
||||
dom.code(function() {
|
||||
dom.text('<');
|
||||
dom.text(self.shortName);
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"');
|
||||
dom.text('>\n</');
|
||||
dom.text(self.shortName);
|
||||
dom.text(dashCase(self.shortName));
|
||||
dom.text('>');
|
||||
});
|
||||
}
|
||||
@@ -384,7 +384,7 @@ Doc.prototype = {
|
||||
dom.text('as attribute');
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' ');
|
||||
dom.text(self.shortName);
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams('\n ', '="', '"', true);
|
||||
dom.text('>\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
@@ -395,7 +395,7 @@ Doc.prototype = {
|
||||
var element = self.element || 'ANY';
|
||||
dom.code(function() {
|
||||
dom.text('<' + element + ' class="');
|
||||
dom.text(self.shortName);
|
||||
dom.text(dashCase(self.shortName));
|
||||
renderParams(' ', ': ', ';', true);
|
||||
dom.text('">\n ...\n');
|
||||
dom.text('</' + element + '>');
|
||||
@@ -467,7 +467,7 @@ Doc.prototype = {
|
||||
(self.param||[]).forEach(function(param){
|
||||
dom.text('\n ');
|
||||
dom.text(param.optional ? ' [' : ' ');
|
||||
dom.text(param.name);
|
||||
dom.text(dashCase(param.name));
|
||||
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="{' + param.type + '}"');
|
||||
dom.text(param.optional ? ']' : '');
|
||||
});
|
||||
@@ -677,8 +677,15 @@ var KEYWORD_PRIORITY = {
|
||||
'.angular.module.ngMock': 8,
|
||||
'.dev_guide.overview': 1,
|
||||
'.dev_guide.bootstrap': 2,
|
||||
'.dev_guide.bootstrap.auto_bootstrap': 1,
|
||||
'.dev_guide.bootstrap.manual_bootstrap': 2,
|
||||
'.dev_guide.mvc': 3,
|
||||
'.dev_guide.mvc.understanding_model': 1,
|
||||
'.dev_guide.mvc.understanding_controller': 2,
|
||||
'.dev_guide.mvc.understanding_view': 3,
|
||||
'.dev_guide.scopes': 4,
|
||||
'.dev_guide.scopes.understanding_scopes': 1,
|
||||
'.dev_guide.scopes.internals': 2,
|
||||
'.dev_guide.compiler': 5,
|
||||
'.dev_guide.templates': 6,
|
||||
'.dev_guide.services': 7,
|
||||
@@ -815,3 +822,11 @@ function property(name) {
|
||||
return value[name];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var DASH_CASE_REGEXP = /[A-Z]/g;
|
||||
function dashCase(name){
|
||||
return name.replace(DASH_CASE_REGEXP, function(letter, pos) {
|
||||
return (pos ? '-' : '') + letter.toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ angular.module('ngdocs.directives', [], function($compileProvider) {
|
||||
|
||||
var angularJsUrl;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
var angularJsRegex = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
|
||||
var angularJsRegex = /^(|.*\/)angular(-\d.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
|
||||
for(var j = 0; j < scripts.length; j++) {
|
||||
var src = scripts[j].src;
|
||||
if (src && src.match(angularJsRegex)) {
|
||||
angularJsUrl = src.replace('docs.angularjs.org', 'code.angularjs.org');
|
||||
angularJsUrl = src.replace(/docs(-next)?\.angularjs\.org/, 'code.angularjs.org');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -291,14 +291,14 @@ angular.module('ngdocs.directives', [], function($compileProvider) {
|
||||
} else {
|
||||
prevStep = 'step_' + pad(step - 1);
|
||||
nextStep = 'step_' + pad(step + 1);
|
||||
codeDiff = 'step-' + step + '...step-' + step;
|
||||
codeDiff = 'step-' + (step-1) + '...step-' + step;
|
||||
}
|
||||
|
||||
content = angular.element(
|
||||
'<li><a href="#!/tutorial/' + prevStep + '">Previous</a></li>' +
|
||||
'<li><a href="tutorial/' + prevStep + '">Previous</a></li>' +
|
||||
'<li><a href="http://angular.github.com/angular-phonecat/step-' + step + '/app">Live Demo</a></li>' +
|
||||
'<li><a href="https://github.com/angular/angular-phonecat/compare/' + codeDiff + '">Code Diff</a></li>' +
|
||||
'<li><a href="#!/tutorial/' + nextStep + '">Next</a></li>'
|
||||
'<li><a href="tutorial/' + nextStep + '">Next</a></li>'
|
||||
);
|
||||
|
||||
element.attr('id', 'tutorial-nav');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
<head>
|
||||
<title><angular/> Docs Scenario Runner</title>
|
||||
<title>AngularJS Docs E2E Test Runner</title>
|
||||
<script type="text/javascript" src="../angular-scenario.js" ng:autotest></script>
|
||||
<script type="text/javascript" src="docs-scenario.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -518,3 +518,24 @@ td.empty-corner-lt {
|
||||
.even {
|
||||
background-color: #d3d3d3;
|
||||
}
|
||||
|
||||
/* tutorial */
|
||||
|
||||
#content .tutorial-start {
|
||||
-moz-border-radius:8px;
|
||||
-webkit-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
|
||||
background-color: #7989D6;
|
||||
padding: 10px;
|
||||
margin-left: 43%;
|
||||
width: 100px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
|
||||
#content .tutorial-start:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ function DocsController(scope, $location, $window, $cookies, $filter) {
|
||||
|
||||
function loadDisqus(currentPageId) {
|
||||
// http://docs.disqus.com/help/2/
|
||||
window.disqus_shortname = 'angularjs';
|
||||
window.disqus_shortname = 'angularjs-next';
|
||||
window.disqus_identifier = currentPageId;
|
||||
window.disqus_url = 'http://docs-next.angularjs.org' + currentPageId;
|
||||
|
||||
@@ -133,20 +133,21 @@ SyntaxHighlighter['defaults'].gutter = true;
|
||||
* @param $cookieStore
|
||||
* @constructor
|
||||
*/
|
||||
function TutorialInstructionsCtrl($cookieStore) {
|
||||
this.selected = $cookieStore.get('selEnv') || 'git-mac';
|
||||
function TutorialInstructionsCtrl($scope, $cookieStore) {
|
||||
$scope.selected = $cookieStore.get('selEnv') || 'git-mac';
|
||||
|
||||
this.currentCls = function(id, cls) {
|
||||
$scope.currentCls = function(id, cls) {
|
||||
return this.selected == id ? cls || 'current' : '';
|
||||
};
|
||||
|
||||
this.select = function(id) {
|
||||
$scope.select = function(id) {
|
||||
this.selected = id;
|
||||
$cookieStore.put('selEnv', id);
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('ngdocs', ['ngdocs.directives'], function($locationProvider, $filterProvider, $compileProvider) {
|
||||
angular.module('ngdocs', ['ngdocs.directives', 'ngResource', 'ngCookies', 'ngSanitize'],
|
||||
function($locationProvider, $filterProvider, $compileProvider) {
|
||||
$locationProvider.html5Mode(true).hashPrefix('!');
|
||||
|
||||
$filterProvider.register('title', function(){
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
baseUrl = location.href.replace(rUrl, indexFile),
|
||||
jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
|
||||
debug = /index[^\.]*-debug\.html$/.test(baseUrl),
|
||||
angularPath = debug ? '../angular.js' : '../angular.min.js',
|
||||
headEl = document.getElementsByTagName('head')[0],
|
||||
sync = true;
|
||||
|
||||
@@ -27,11 +26,18 @@
|
||||
addTag('link', {rel: 'stylesheet', href: 'syntaxhighlighter/syntaxhighlighter-combined.css',
|
||||
type: 'text/css'});
|
||||
addTag('script', {src: 'syntaxhighlighter/syntaxhighlighter-combined.js'}, sync);
|
||||
if (jQuery) addTag('script', {src: 'jquery.min.js'});
|
||||
addTag('script', {src: angularPath}, sync);
|
||||
if (jQuery) addTag('script', {src: debug ? 'jquery.js' : 'jquery.min.js'});
|
||||
addTag('script', {src: path('angular.js')}, sync);
|
||||
addTag('script', {src: path('angular-resource.js') }, sync);
|
||||
addTag('script', {src: path('angular-cookies.js') }, sync);
|
||||
addTag('script', {src: path('angular-sanitize.js') }, sync);
|
||||
addTag('script', {src: 'docs-combined.js'}, sync);
|
||||
addTag('script', {src: 'docs-keywords.js'}, sync);
|
||||
|
||||
function path(name) {
|
||||
return '../' + name.replace(/\.js$/, debug ? '.js' : '.min.js');
|
||||
}
|
||||
|
||||
function addTag(name, attributes, sync) {
|
||||
var el = document.createElement(name),
|
||||
attrName;
|
||||
@@ -93,7 +99,7 @@
|
||||
<ul id="navbar">
|
||||
<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="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>
|
||||
@@ -101,7 +107,7 @@
|
||||
|
||||
<div id="sidebar">
|
||||
<input type="text" ng:model="search" id="search-box" placeholder="search the docs"
|
||||
tabindex="1" accesskey="s" ng:model-instant>
|
||||
tabindex="1" accesskey="s">
|
||||
|
||||
<ul id="content-list" ng:class="sectionId" ng:cloak>
|
||||
<li ng:repeat="page in pages | filter:search" ng:class="getClass(page)">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../../lib/jquery/jquery.js
|
||||
@@ -1,28 +1,31 @@
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" ng:app>
|
||||
<html ng-app>
|
||||
<head>
|
||||
<title>Personal Log</title>
|
||||
<script type="text/javascript" src="../../src/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="personalLog.js"></script>
|
||||
<script src="../../src/loader.js"></script>
|
||||
<script>
|
||||
setupModuleLoader(window);
|
||||
</script>
|
||||
<script src="personalLog.js"></script>
|
||||
<script src="../../src/angular-bootstrap.js"></script>
|
||||
<script src="../../src/ngCookies/cookies.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<!-- TODO: we need to expose $root so that we can delete cookies in the scenario runner, there
|
||||
must be a better way to do this -->
|
||||
<body ng:controller="example.personalLog.LogCtrl">
|
||||
<body ng-controller="LogCtrl">
|
||||
|
||||
<form action="" ng:submit="addLog(newMsg)">
|
||||
<input type="text" ng:model="newMsg" />
|
||||
<input type="submit" value="add" />
|
||||
<input type="button" value="remove all" ng:click="rmLogs()" />
|
||||
<form action="" ng-submit="addLog(newMsg)">
|
||||
<input type="text" ng-model="newMsg">
|
||||
<input type="submit" value="add">
|
||||
<input type="button" value="remove all" ng-click="rmLogs()">
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h2>Logs:</h2>
|
||||
<ul>
|
||||
<li ng:repeat="log in logs | orderBy:'-at'">
|
||||
<li ng-repeat="log in logs | orderBy:'-at'">
|
||||
{{log.at | date:'yy-MM-dd HH:mm'}} {{log.msg}}
|
||||
[<a href="" ng:click="rmLog(log)">x</a>]
|
||||
[<a href="" ng-click="rmLog(log)">x</a>]
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -5,28 +5,23 @@
|
||||
* - testability of controllers
|
||||
* - dependency injection for controllers via $inject and constructor function
|
||||
* - $cookieStore for persistent cookie-backed storage
|
||||
* - simple templating constructs such as ng:repeat and {{}}
|
||||
* - simple templating constructs such as ng-repeat and {{}}
|
||||
* - date filter
|
||||
* - and binding onSubmit and onClick events to angular expressions
|
||||
* @author Igor Minar
|
||||
*/
|
||||
|
||||
|
||||
/** @namespace the 'example' namespace */
|
||||
var example = example || {};
|
||||
/** @namespace namespace of the personal log app */
|
||||
example.personalLog = {};
|
||||
|
||||
|
||||
//name space isolating closure
|
||||
(function() {
|
||||
|
||||
var app = angular.module('personalLog', ['ngCookies']);
|
||||
|
||||
var LOGS = 'logs';
|
||||
|
||||
/**
|
||||
* The controller for the personal log app.
|
||||
*/
|
||||
function LogCtrl($cookieStore, $scope) {
|
||||
app.controller('LogCtrl', ['$cookieStore', '$scope', function LogCtrl($cookieStore, $scope) {
|
||||
|
||||
var logs = $scope.logs = $cookieStore.get(LOGS) || []; //main model
|
||||
|
||||
@@ -72,11 +67,6 @@ function LogCtrl($cookieStore, $scope) {
|
||||
logs.splice(0, logs.length);
|
||||
$cookieStore.remove(LOGS);
|
||||
};
|
||||
}
|
||||
}]);
|
||||
|
||||
//inject
|
||||
LogCtrl.$inject = ['$cookieStore', '$scope'];
|
||||
|
||||
//export
|
||||
example.personalLog.LogCtrl = LogCtrl;
|
||||
})();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
describe('example.personalLog.LogCtrl', function() {
|
||||
var logScope;
|
||||
|
||||
beforeEach(function() {
|
||||
var injector = angular.injector(['ng', 'ngMock']);
|
||||
logScope = injector.get('$rootScope');
|
||||
logScope.$cookies = injector.get('$cookies');
|
||||
injector.instantiate(example.personalLog.LogCtrl, {$scope: logScope});
|
||||
});
|
||||
|
||||
beforeEach(module('personalLog'));
|
||||
|
||||
beforeEach(inject(function($rootScope, $controller) {
|
||||
logScope = $rootScope.$new();
|
||||
$controller('LogCtrl', {$scope: logScope});
|
||||
}));
|
||||
|
||||
|
||||
it('should initialize notes with an empty array', function() {
|
||||
@@ -43,11 +44,11 @@ describe('example.personalLog.LogCtrl', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should store logs in the logs cookie', function() {
|
||||
expect(logScope.$cookies.logs).not.toBeDefined();
|
||||
it('should store logs in the logs cookie', inject(function($cookies) {
|
||||
expect($cookies.logs).not.toBeDefined();
|
||||
logScope.addLog('first log message');
|
||||
expect(logScope.$cookies.logs).toBeTruthy();
|
||||
});
|
||||
expect($cookies.logs).toBeTruthy();
|
||||
}));
|
||||
|
||||
|
||||
it('should do nothing if newMsg is empty', function() {
|
||||
@@ -79,17 +80,17 @@ describe('example.personalLog.LogCtrl', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should update cookies when a log is deleted', function() {
|
||||
expect(logScope.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){3}\]/);
|
||||
it('should update cookies when a log is deleted', inject(function($cookies) {
|
||||
expect($cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){3}\]/);
|
||||
|
||||
logScope.rmLog(logScope.logs[1]);
|
||||
expect(logScope.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){2}\]/);
|
||||
expect($cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){2}\]/);
|
||||
|
||||
logScope.rmLog(logScope.logs[0]);
|
||||
logScope.rmLog(logScope.logs[0]);
|
||||
logScope.rmLog(logScope.logs[0]);
|
||||
expect(logScope.$cookies.logs).toMatch(/\[\]/);
|
||||
});
|
||||
expect($cookies.logs).toMatch(/\[\]/);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -110,10 +111,10 @@ describe('example.personalLog.LogCtrl', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should remove logs cookie', function() {
|
||||
expect(logScope.$cookies.logs).toBeTruthy();
|
||||
it('should remove logs cookie', inject(function($cookies) {
|
||||
expect($cookies.logs).toBeTruthy();
|
||||
logScope.rmLogs();
|
||||
expect(logScope.$cookies.logs).not.toBeDefined();
|
||||
});
|
||||
expect($cookies.logs).not.toBeDefined();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" ng:app>
|
||||
<html ng-app="example">
|
||||
<head>
|
||||
<title>angular dev sandbox</title>
|
||||
<script src="../src/angular-bootstrap.js"></script>
|
||||
<script src="../src/loader.js"></script>
|
||||
<script>
|
||||
angular.module.ng('routeConfig', function($route) {
|
||||
$route.when('/view1', {controller: MyCtrl, template: 'view1.html'});
|
||||
$route.when('/view2', {controller: MyCtrl, template: 'view2.html'});
|
||||
setupModuleLoader(window);
|
||||
angular.module('example', [], function($routeProvider) {
|
||||
$routeProvider.when('/view1', {controller: MyCtrl, template: 'view1.html'});
|
||||
$routeProvider.when('/view2', {controller: MyCtrl, template: 'view2.html'});
|
||||
|
||||
function MyCtrl() {};
|
||||
}, {$inject: ['$route']});
|
||||
function MyCtrl($location, $scope) {
|
||||
$scope.url = function() {
|
||||
return $location.url();
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<script src="../src/angular-bootstrap.js"></script>
|
||||
</head>
|
||||
<body ng:init="$service('$window').$root = this">
|
||||
<body>
|
||||
<p>
|
||||
<a href="#/view1">view1</a> | <a href="#/view2">view2</a> | <a href="#">blank</a>
|
||||
</p>
|
||||
|
||||
view: <ng:view></ng:view>
|
||||
<hr>
|
||||
|
||||
<div ng-view></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
view1<br>
|
||||
location: {{$service('$location').href}}
|
||||
location: {{url()}}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
view2<br/>
|
||||
location: {{$service('$location').href}}<br/>
|
||||
location: {{url()}}<br/>
|
||||
|
||||
@@ -16,7 +16,7 @@ fs.readFile('angularFiles.js', function(err, data) {
|
||||
fs.writeFile('./jsTestDriver.conf', prefix + combine(angularFiles.jstd,
|
||||
angularFiles.jstdExclude));
|
||||
|
||||
fs.writeFile('./jsTestDriver-mocks.conf', prefix + combine(angularFiles.jstdMocks));
|
||||
fs.writeFile('./jsTestDriver-modules.conf', prefix + combine(angularFiles.jstdModules));
|
||||
|
||||
fs.writeFile('./jsTestDriver-scenario.conf', prefixScenario +
|
||||
combine(angularFiles.jstdScenario) +
|
||||
@@ -40,6 +40,8 @@ function combine(load, exclude) {
|
||||
if (exclude) fileList += ('\n\nexclude:\n- ' + exclude.join('\n- '));
|
||||
|
||||
//Replace placeholders for src list before returning
|
||||
return fileList.replace(/@angularSrc/g, angularSrc);
|
||||
return fileList.replace(/@(.*)/g, function(all, alias) {
|
||||
return angularFiles[alias].join('\n- ');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ describe("localized filters", function() {
|
||||
expect(binding('input | date:"medium"')).toBe('03/06/1977 18:07:23');
|
||||
expect(binding('input | date:"longDate"')).toBe("3 de junio de 1977");
|
||||
expect(binding('input | number')).toBe('234.234.443.432');
|
||||
expect(binding('input | currency')).toBe('€ 234.234.443.432,00');
|
||||
expect(binding('input | currency')).toBe('€\u00a0234.234.443.432,00');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,8 +20,8 @@ describe("localized filters", function() {
|
||||
it('should check filters for cs locale', function() {
|
||||
expect(binding('input | date:"medium"')).toBe('3.6.1977 18:07:23');
|
||||
expect(binding('input | date:"longDate"')).toBe("3. června 1977");
|
||||
expect(binding('input | number')).toBe('234 234 443 432');
|
||||
expect(binding('input | currency')).toBe('234 234 443 432,00 K\u010d');
|
||||
expect(binding('input | number')).toBe('234\u00a0234\u00a0443\u00a0432');
|
||||
expect(binding('input | currency')).toBe('234\u00a0234\u00a0443\u00a0432,00\u00a0K\u010d');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("localized filters", function() {
|
||||
expect(binding('input | date:"medium"')).toBe('03.06.1977 18:07:23');
|
||||
expect(binding('input | date:"longDate"')).toBe("3. Juni 1977");
|
||||
expect(binding('input | number')).toBe('234.234.443.432');
|
||||
expect(binding('input | currency')).toBe('234.234.443.432,00 €');
|
||||
expect(binding('input | currency')).toBe('234.234.443.432,00\u00a0€');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,42 +53,42 @@ describe("localized filters", function() {
|
||||
|
||||
describe('ng:pluralize for en locale', function() {
|
||||
it('should show pluralized strings', function() {
|
||||
expect(element('.ng-pluralize:first').html()).toBe('You have one email!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('You have one email!');
|
||||
|
||||
input('plInput').enter('0');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('You have no email!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('You have no email!');
|
||||
|
||||
input('plInput').enter('3');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('You have 3 emails!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('You have 3 emails!');
|
||||
});
|
||||
|
||||
it('should show pluralized strings with offsets', function() {
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Shanjian is viewing!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Shanjian is viewing!');
|
||||
|
||||
input('plInput2').enter('0');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Nobody is viewing!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Nobody is viewing!');
|
||||
|
||||
input('plInput2').enter('2');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
|
||||
|
||||
input('plInput2').enter('3');
|
||||
expect(element('.ng-pluralize:last').html()).
|
||||
expect(element('ng-pluralize:last').html()).
|
||||
toBe('Shanjian, Di and one other person are viewing!');
|
||||
|
||||
input('plInput2').enter('4');
|
||||
expect(element('.ng-pluralize:last').html()).
|
||||
expect(element('ng-pluralize:last').html()).
|
||||
toBe('Shanjian, Di and 2 other people are viewing!');
|
||||
});
|
||||
|
||||
it('should show pluralized strings with correct data-binding', function() {
|
||||
input('plInput2').enter('2');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
|
||||
|
||||
input('person1').enter('Igor');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Igor and Di are viewing!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Igor and Di are viewing!');
|
||||
|
||||
input('person2').enter('Vojta');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Igor and Vojta are viewing!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Igor and Vojta are viewing!');
|
||||
});
|
||||
})
|
||||
});
|
||||
@@ -101,26 +101,26 @@ describe("localized filters", function() {
|
||||
it('should check filters for sk locale', function() {
|
||||
expect(binding('input | date:"medium"')).toBe('3.6.1977 18:07:23');
|
||||
expect(binding('input | date:"longDate"')).toBe("3. júna 1977");
|
||||
expect(binding('input | number')).toBe('234 234 443 432');
|
||||
expect(binding('input | currency')).toBe('234 234 443 432,00 Sk');
|
||||
expect(binding('input | number')).toBe('234\u00a0234\u00a0443\u00a0432');
|
||||
expect(binding('input | currency')).toBe('234\u00a0234\u00a0443\u00a0432,00\u00a0Sk');
|
||||
});
|
||||
|
||||
|
||||
describe('ng:pluralize for sk locale', function() {
|
||||
it('should show pluralized strings', function() {
|
||||
expect(element('.ng-pluralize').html()).toBe('Mas jeden email!');
|
||||
expect(element('ng-pluralize').html()).toBe('Mas jeden email!');
|
||||
|
||||
input('plInput').enter('0');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('Mas 0 emailov!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('Mas 0 emailov!');
|
||||
|
||||
input('plInput').enter('3');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('Mas 3 emaily!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('Mas 3 emaily!');
|
||||
|
||||
input('plInput').enter('4');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('Mas 4 emaily!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('Mas 4 emaily!');
|
||||
|
||||
input('plInput').enter('6');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('Mas 6 emailov!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('Mas 6 emailov!');
|
||||
});
|
||||
|
||||
it('should show pluralized strings with offsets', function() {
|
||||
@@ -147,38 +147,38 @@ describe("localized filters", function() {
|
||||
|
||||
describe('ng:pluralize for zh locale', function() {
|
||||
it('should show pluralized strings', function() {
|
||||
expect(element('.ng-pluralize:first').html()).toBe('1人在浏览该文件!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('1人在浏览该文件!');
|
||||
|
||||
input('plInput').enter('0');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('0人在浏览该文件!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('0人在浏览该文件!');
|
||||
|
||||
input('plInput').enter('3');
|
||||
expect(element('.ng-pluralize:first').html()).toBe('3人在浏览该文件!');
|
||||
expect(element('ng-pluralize:first').html()).toBe('3人在浏览该文件!');
|
||||
});
|
||||
|
||||
it('should show pluralized strings with offsets', function() {
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Shanjian 在浏览该文件!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Shanjian 在浏览该文件!');
|
||||
|
||||
input('plInput2').enter('0');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('没有人在浏览该文件!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('没有人在浏览该文件!');
|
||||
|
||||
input('plInput2').enter('2');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
|
||||
|
||||
input('plInput2').enter('3');
|
||||
expect(element('.ng-pluralize:last').html()).
|
||||
expect(element('ng-pluralize:last').html()).
|
||||
toBe('Shanjian, Di 还有其他1 人在浏览该文件!');
|
||||
});
|
||||
|
||||
it('should show pluralized strings with correct data-binding', function() {
|
||||
input('plInput2').enter('2');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
|
||||
|
||||
input('person1').enter('彭迪');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('彭迪 和 Di 在浏览该文件!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('彭迪 和 Di 在浏览该文件!');
|
||||
|
||||
input('person2').enter('一哥');
|
||||
expect(element('.ng-pluralize:last').html()).toBe('彭迪 和 一哥 在浏览该文件!');
|
||||
expect(element('ng-pluralize:last').html()).toBe('彭迪 和 一哥 在浏览该文件!');
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<script src="../../build/angular.js"></script>
|
||||
<script src="../../build/i18n/angular-locale_cs.js"></script>
|
||||
<script>
|
||||
function AppCntl(){
|
||||
this.input = 234234443432;
|
||||
function AppCntl($scope){
|
||||
$scope.input = 234234443432;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body ng:controller="AppCntl">
|
||||
<input type="text" ng:model="input"><br>
|
||||
<body ng-controller="AppCntl">
|
||||
<input type="text" ng-model="input"><br>
|
||||
date: {{input | date:"medium"}}<br>
|
||||
date: {{input | date:"longDate"}}<br>
|
||||
number: {{input | number}}<br>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<script src="../../build/angular.js"></script>
|
||||
<script src="../../build/i18n/angular-locale_de.js"></script>
|
||||
<script>
|
||||
function AppCntl(){
|
||||
this.input = 234234443432;
|
||||
function AppCntl($scope){
|
||||
$scope.input = 234234443432;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body ng:controller="AppCntl">
|
||||
<input type="text" ng:model="input"><br>
|
||||
<body ng-controller="AppCntl">
|
||||
<input type="text" ng-model="input"><br>
|
||||
date: {{input | date:"medium"}}<br>
|
||||
date: {{input | date:"longDate"}}<br>
|
||||
number: {{input | number}}<br>
|
||||
|
||||
@@ -8,41 +8,41 @@
|
||||
<script src="../../build/i18n/angular-locale_en.js"></script>
|
||||
-->
|
||||
<script>
|
||||
function AppCntl(){
|
||||
this.input = 234234443432;
|
||||
this.plInput = 1;
|
||||
this.person1 = "Shanjian";
|
||||
this.person2 = "Di";
|
||||
this.plInput2 = 1;
|
||||
function AppCntl($scope){
|
||||
$scope.input = 234234443432;
|
||||
$scope.plInput = 1;
|
||||
$scope.person1 = "Shanjian";
|
||||
$scope.person2 = "Di";
|
||||
$scope.plInput2 = 1;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body ng:controller="AppCntl">
|
||||
<body ng-controller="AppCntl">
|
||||
<h3>Datetime/Number/Currency filters demo:</h3>
|
||||
<input type="text" ng:model="input" value="234234443432"><br>
|
||||
<input type="text" ng-model="input" value="234234443432"><br>
|
||||
date(medium): {{input | date:"medium"}}<br>
|
||||
date(longDate): {{input | date:"longDate"}}<br>
|
||||
number: {{input | number}}<br>
|
||||
currency: {{input | currency }}
|
||||
<hr/>
|
||||
<h3>Pluralization demo:</h3>
|
||||
<input type="text" ng:model="plInput"><br>
|
||||
<ng:pluralize count="plInput"
|
||||
<input type="text" ng-model="plInput"><br>
|
||||
<ng-pluralize count="plInput"
|
||||
when= "{ '0': 'You have no email!',
|
||||
'one': 'You have one email!',
|
||||
'other': 'You have {} emails!'}">
|
||||
</ng:pluralize>
|
||||
</ng-pluralize>
|
||||
<hr/>
|
||||
<h3>Pluralization demo with offsets:</h3>
|
||||
Name of person1:<input type="text" ng:model="person1"/><br/>
|
||||
Name of person2:<input type="text" ng:model="person2"/><br/>
|
||||
<input type="text" ng:model="plInput2"><br>
|
||||
<ng:pluralize count="plInput2" offset=2
|
||||
Name of person1:<input type="text" ng-model="person1"/><br/>
|
||||
Name of person2:<input type="text" ng-model="person2"/><br/>
|
||||
<input type="text" ng-model="plInput2"><br>
|
||||
<ng-pluralize count="plInput2" offset=2
|
||||
when= "{'0':'Nobody is viewing!',
|
||||
'1': '{{person1}} is viewing!',
|
||||
'2': '{{person1}} and {{person2}} are viewing!',
|
||||
'3': '{{person1}}, {{person2}} and one other person are viewing!',
|
||||
'other': '{{person1}}, {{person2}} and {} other people are viewing!'}">
|
||||
</ng:pluralize>
|
||||
</ng-pluralize>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<script src="../../build/angular.js"></script>
|
||||
<script src="../../build/i18n/angular-locale_es.js"></script>
|
||||
<script>
|
||||
function AppCntl(){
|
||||
this.input = 234234443432;
|
||||
function AppCntl($scope){
|
||||
$scope.input = 234234443432;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body ng:controller="AppCntl">
|
||||
<input type="text" ng:model="input" value="234234443432"><br>
|
||||
<body ng-controller="AppCntl">
|
||||
<input type="text" ng-model="input" value="234234443432"><br>
|
||||
date: {{input | date:"medium"}}<br>
|
||||
date: {{input | date:"longDate"}}<br>
|
||||
number: {{input | number}}<br>
|
||||
|
||||
@@ -6,24 +6,24 @@
|
||||
<script src="../../build/angular.js"></script>
|
||||
<script src="../../build/i18n/angular-locale_sk-sk.js"></script>
|
||||
<script>
|
||||
function AppCntl(){
|
||||
this.input = 234234443432;
|
||||
this.plInput = 1;
|
||||
function AppCntl($scope){
|
||||
$scope.input = 234234443432;
|
||||
$scope.plInput = 1;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body ng:controller="AppCntl">
|
||||
<input type="text" ng:model="input" value="234234443432"><br>
|
||||
<body ng-controller="AppCntl">
|
||||
<input type="text" ng-model="input" value="234234443432"><br>
|
||||
date: {{input | date:"medium"}}<br>
|
||||
date: {{input | date:"longDate"}}<br>
|
||||
number: {{input | number}}<br>
|
||||
currency: {{input | currency }}
|
||||
<hr/>
|
||||
<input type="text" ng:model="plInput"><br>
|
||||
<ng:pluralize count="plInput"
|
||||
<input type="text" ng-model="plInput"><br>
|
||||
<ng-pluralize count="plInput"
|
||||
when= "{ 'one': 'Mas jeden email!',
|
||||
'few': 'Mas {} emaily!',
|
||||
'other': 'Mas {} emailov!'}">
|
||||
</ng:pluralize>
|
||||
</ng-pluralize>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,38 +6,38 @@
|
||||
<script src="../../build/angular.js"></script>
|
||||
<script src="../../build/i18n/angular-locale_zh-cn.js"></script>
|
||||
<script>
|
||||
function AppCntl(){
|
||||
this.input = 234234443432;
|
||||
this.plInput = 1;
|
||||
this.person1 = "Shanjian";
|
||||
this.person2 = "Di";
|
||||
this.plInput2 = 1;
|
||||
function AppCntl($scope){
|
||||
$scope.input = 234234443432;
|
||||
$scope.plInput = 1;
|
||||
$scope.person1 = "Shanjian";
|
||||
$scope.person2 = "Di";
|
||||
$scope.plInput2 = 1;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body ng:controller="AppCntl">
|
||||
<body ng-controller="AppCntl">
|
||||
<h3>Datetime/Number/Currency filters demo:</h3>
|
||||
<input type="text" ng:model="input"><br>
|
||||
<input type="text" ng-model="input"><br>
|
||||
date(medium): {{input | date:"medium"}}<br>
|
||||
date(longDate): {{input | date:"longDate"}}<br>
|
||||
number: {{input | number}}<br>
|
||||
currency: {{input | currency }}
|
||||
<hr/>
|
||||
<h3>Pluralization demo:</h3>
|
||||
<input type="text" ng:model="plInput"><br>
|
||||
<ng:pluralize count="plInput"
|
||||
<input type="text" ng-model="plInput"><br>
|
||||
<ng-pluralize count="plInput"
|
||||
when= "{'other':'{}人在浏览该文件!'}">
|
||||
</ng:pluralize>
|
||||
</ng-pluralize>
|
||||
<hr/>
|
||||
<h3>Pluralization demo with offsets:</h3>
|
||||
Name of person1:<input type="text" ng:model="person1"/><br/>
|
||||
Name of person2:<input type="text" ng:model="person2"/><br/>
|
||||
<input type="text" ng:model="plInput2"><br>
|
||||
<ng:pluralize count="plInput2" offset=2
|
||||
Name of person1:<input type="text" ng-model="person1"/><br/>
|
||||
Name of person2:<input type="text" ng-model="person2"/><br/>
|
||||
<input type="text" ng-model="plInput2"><br>
|
||||
<ng-pluralize count="plInput2" offset=2
|
||||
when= "{'0':'没有人在浏览该文件!',
|
||||
'1': '{{person1}} 在浏览该文件!',
|
||||
'2': '{{person1}} 和 {{person2}} 在浏览该文件!',
|
||||
'other': '{{person1}}, {{person2}} 还有其他{} 人在浏览该文件!'}">
|
||||
</ng:pluralize>
|
||||
</ng-pluralize>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -12,7 +12,7 @@ var Q = require('qq'),
|
||||
require: function() {},
|
||||
i18n: {currency: {}, pluralRules: {}} };
|
||||
|
||||
createFolder('../locale/').then(function() {
|
||||
createFolder('../../src/ngLocale/').then(function() {
|
||||
var promiseA = Q.defer(),
|
||||
promiseB = Q.defer();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>ApplicationVersion</key>
|
||||
<array>
|
||||
<string>com.omnigroup.OmniGrafflePro</string>
|
||||
<string>138.17.0.133677</string>
|
||||
<string>138.33.0.157554</string>
|
||||
</array>
|
||||
<key>AutoAdjust</key>
|
||||
<true/>
|
||||
@@ -46,7 +46,7 @@
|
||||
<key>DisplayScale</key>
|
||||
<string>1 0/72 in = 1.0000 in</string>
|
||||
<key>GraphDocumentVersion</key>
|
||||
<integer>6</integer>
|
||||
<integer>8</integer>
|
||||
<key>GraphicsList</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -95,7 +95,7 @@
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -153,12 +153,12 @@
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
|
||||
\f0\b\fs24 \cf0 ng:repeat}</string>
|
||||
\f0\b\fs24 \cf0 ng-repeat}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
@@ -242,7 +242,7 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -364,7 +364,7 @@
|
||||
<key>Pad</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -435,12 +435,12 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\f0\b\fs24 \cf0 ng:controller}</string>
|
||||
\f0\b\fs24 \cf0 ng-controller}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
@@ -489,6 +489,11 @@
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -496,7 +501,7 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -813,7 +818,7 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -863,7 +868,7 @@
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -900,6 +905,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -968,7 +978,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1020,7 +1030,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1056,6 +1066,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -1124,7 +1139,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1176,7 +1191,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1212,6 +1227,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -1263,7 +1283,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1349,6 +1369,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1356,12 +1381,12 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\f0\b\fs20 \cf0 <li ng:repeat="phone in phones">\
|
||||
\f0\b\fs20 \cf0 <li ng-repeat="phone in phones">\
|
||||
\{\{phone.name\}\}\
|
||||
<p>\{\{phone.snippet\}\}</p>\
|
||||
</li>}</string>
|
||||
@@ -1409,6 +1434,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1416,7 +1446,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1498,7 +1528,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1551,7 +1581,7 @@ Scope}</string>
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -1587,6 +1617,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -1677,6 +1712,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1684,13 +1724,13 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\f0\b\fs20 \cf0 <body \
|
||||
ng:controller=\
|
||||
ng-controller=\
|
||||
"PhoneListCtrl">}</string>
|
||||
</dict>
|
||||
<key>TextPlacement</key>
|
||||
@@ -1728,12 +1768,12 @@ Scope}</string>
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
|
||||
\f0\fs24 \cf0 ng:autobind}</string>
|
||||
\f0\fs24 \cf0 ng-app}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
@@ -1851,7 +1891,7 @@ Scope}</string>
|
||||
<key>Pad</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -1903,6 +1943,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1910,7 +1955,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1960,6 +2005,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1967,7 +2017,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -2017,6 +2067,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -2024,7 +2079,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -2074,7 +2129,7 @@ Scope}</string>
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -2110,6 +2165,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -2162,7 +2222,7 @@ Scope}</string>
|
||||
<key>Pad</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -2333,9 +2393,9 @@ Scope}</string>
|
||||
<key>MasterSheets</key>
|
||||
<array/>
|
||||
<key>ModificationDate</key>
|
||||
<string>2011-05-10 11:41:32 -0700</string>
|
||||
<string>2012-04-04 15:31:40 -0700</string>
|
||||
<key>Modifier</key>
|
||||
<string>Kenneth Culp</string>
|
||||
<string>Igor Minar</string>
|
||||
<key>NotesVisible</key>
|
||||
<string>NO</string>
|
||||
<key>Orientation</key>
|
||||
@@ -2363,8 +2423,8 @@ Scope}</string>
|
||||
</array>
|
||||
<key>NSPaperSize</key>
|
||||
<array>
|
||||
<string>size</string>
|
||||
<string>{792, 612}</string>
|
||||
<string>coded</string>
|
||||
<string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAx7X05TU2l6ZT1mZn2WgRgDgWQChg==</string>
|
||||
</array>
|
||||
<key>NSRightMargin</key>
|
||||
<array>
|
||||
@@ -2409,7 +2469,7 @@ Scope}</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>Frame</key>
|
||||
<string>{{1, 56}, {1286, 822}}</string>
|
||||
<string>{{865, 756}, {1286, 822}}</string>
|
||||
<key>ListView</key>
|
||||
<true/>
|
||||
<key>OutlineWidth</key>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>ApplicationVersion</key>
|
||||
<array>
|
||||
<string>com.omnigroup.OmniGrafflePro</string>
|
||||
<string>138.17.0.133677</string>
|
||||
<string>138.33.0.157554</string>
|
||||
</array>
|
||||
<key>AutoAdjust</key>
|
||||
<true/>
|
||||
@@ -46,7 +46,7 @@
|
||||
<key>DisplayScale</key>
|
||||
<string>1 0/72 in = 1.0000 in</string>
|
||||
<key>GraphDocumentVersion</key>
|
||||
<integer>6</integer>
|
||||
<integer>8</integer>
|
||||
<key>GraphicsList</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -95,7 +95,7 @@
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -206,12 +206,12 @@
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
|
||||
\f0\fs24 \cf0 ng:repeat}</string>
|
||||
\f0\fs24 \cf0 ng-repeat}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
@@ -335,7 +335,7 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -353,7 +353,7 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>Bounds</key>
|
||||
<string>{{46.269, 187.718}, {120.418, 19.3087}}</string>
|
||||
<string>{{49.3198, 187.718}, {152.037, 19.3087}}</string>
|
||||
<key>Class</key>
|
||||
<string>ShapedGraphic</string>
|
||||
<key>ID</key>
|
||||
@@ -385,12 +385,12 @@
|
||||
<key>Pad</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\f0\b\fs20 \cf0 <input name="query">}</string>
|
||||
\f0\b\fs20 \cf0 <input ng-model="query">}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
@@ -399,7 +399,7 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Bounds</key>
|
||||
<string>{{34.65, 183.374}, {139.675, 25.6266}}</string>
|
||||
<string>{{34.65, 183.374}, {176.35, 25.6266}}</string>
|
||||
<key>Class</key>
|
||||
<string>ShapedGraphic</string>
|
||||
<key>ID</key>
|
||||
@@ -433,7 +433,7 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Bounds</key>
|
||||
<string>{{29, 170.836}, {143.325, 33.164}}</string>
|
||||
<string>{{29, 170.836}, {172, 33.164}}</string>
|
||||
<key>Class</key>
|
||||
<string>ShapedGraphic</string>
|
||||
<key>FontInfo</key>
|
||||
@@ -473,6 +473,11 @@
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -480,7 +485,7 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -522,7 +527,7 @@
|
||||
<key>Pad</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -600,12 +605,12 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\f0\fs24 \cf0 ng:controller}</string>
|
||||
\f0\fs24 \cf0 ng-controller}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
@@ -654,6 +659,11 @@
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -661,7 +671,7 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -972,7 +982,7 @@
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1022,7 +1032,7 @@
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -1059,6 +1069,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -1127,7 +1142,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1179,7 +1194,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1215,6 +1230,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -1283,7 +1303,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1335,7 +1355,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1371,6 +1391,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -1422,7 +1447,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1508,6 +1533,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1515,12 +1545,12 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\f0\fs20 \cf0 <li ng:repeat="phone in phones">\
|
||||
\f0\fs20 \cf0 <li ng-repeat="phone in phones">\
|
||||
\{\{phone.name\}\}\
|
||||
<p>\{\{phone.snippet\}\}</p>\
|
||||
</li>}</string>
|
||||
@@ -1568,6 +1598,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1575,7 +1610,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1655,7 +1690,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -1748,7 +1783,7 @@ Scope}</string>
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -1784,6 +1819,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -1869,6 +1909,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -1876,13 +1921,13 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
|
||||
\f0\fs20 \cf0 <body \
|
||||
ng:controller=\
|
||||
ng-controller=\
|
||||
"PhoneListCtrl">}</string>
|
||||
</dict>
|
||||
<key>TextPlacement</key>
|
||||
@@ -1920,12 +1965,12 @@ Scope}</string>
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
|
||||
\f0\fs24 \cf0 ng:autobind}</string>
|
||||
\f0\fs24 \cf0 ng-app}</string>
|
||||
<key>VerticalPad</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
@@ -2043,7 +2088,7 @@ Scope}</string>
|
||||
<key>Pad</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -2095,6 +2140,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -2102,7 +2152,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -2152,6 +2202,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -2159,7 +2214,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -2209,6 +2264,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>Text</key>
|
||||
@@ -2216,7 +2276,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fmodern\fcharset0 CourierNewPSMT;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -2251,7 +2311,7 @@ Scope}</string>
|
||||
<key>Align</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural
|
||||
@@ -2328,7 +2388,7 @@ Scope}</string>
|
||||
<key>Text</key>
|
||||
<dict>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -2364,6 +2424,11 @@ Scope}</string>
|
||||
<integer>2</integer>
|
||||
<key>GradientAngle</key>
|
||||
<real>90</real>
|
||||
<key>GradientColor</key>
|
||||
<dict>
|
||||
<key>w</key>
|
||||
<string>0.666667</string>
|
||||
</dict>
|
||||
<key>MiddleColor</key>
|
||||
<dict>
|
||||
<key>b</key>
|
||||
@@ -2416,7 +2481,7 @@ Scope}</string>
|
||||
<key>Pad</key>
|
||||
<integer>0</integer>
|
||||
<key>Text</key>
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350
|
||||
<string>{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf360
|
||||
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural
|
||||
@@ -2587,9 +2652,9 @@ Scope}</string>
|
||||
<key>MasterSheets</key>
|
||||
<array/>
|
||||
<key>ModificationDate</key>
|
||||
<string>2011-05-10 11:42:05 -0700</string>
|
||||
<string>2012-04-04 15:30:36 -0700</string>
|
||||
<key>Modifier</key>
|
||||
<string>Kenneth Culp</string>
|
||||
<string>Igor Minar</string>
|
||||
<key>NotesVisible</key>
|
||||
<string>NO</string>
|
||||
<key>Orientation</key>
|
||||
@@ -2617,8 +2682,8 @@ Scope}</string>
|
||||
</array>
|
||||
<key>NSPaperSize</key>
|
||||
<array>
|
||||
<string>size</string>
|
||||
<string>{792, 612}</string>
|
||||
<string>coded</string>
|
||||
<string>BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAx7X05TU2l6ZT1mZn2WgRgDgWQChg==</string>
|
||||
</array>
|
||||
<key>NSRightMargin</key>
|
||||
<array>
|
||||
@@ -2663,7 +2728,7 @@ Scope}</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>Frame</key>
|
||||
<string>{{1, 56}, {1286, 822}}</string>
|
||||
<string>{{885, 756}, {1286, 822}}</string>
|
||||
<key>ListView</key>
|
||||
<true/>
|
||||
<key>OutlineWidth</key>
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 51 KiB |