Compare commits

...

165 Commits

Author SHA1 Message Date
Igor Minar 76afa406b1 chore(release): cut 1.0.0rc7 rc-generation release 2012-04-30 16:32:45 -07:00
Igor Minar f3c77858be docs(changelog): release notes for 1.0.0rc7 rc-generation 2012-04-30 15:53:05 -07:00
Igor Minar 96758c1c52 docs(ngCsp): make the CSP docs publicly visible 2012-04-30 15:37:12 -07:00
Igor Minar 006fb4fbeb docs(ngSanitize): fix directive links 2012-04-30 01:09:55 -07:00
Igor Minar 075c089b5c docs(tutorial): update all the remaining steps
I made some diagrams and portions of the text that are stil stale
invisible. We'll fix these in the next relese.
2012-04-30 01:08:15 -07:00
Igor Minar 2b87c814ab feat($parse): CSP compatibility
CSP (content security policy) forbids apps to use eval or
Function(string) generated functions (among other things). For us to be
compatible, we just need to implement the "getterFn" in $parse without
violating any of these restrictions.

We currently use Function(string) generated functions as a speed
optimization. With this change, it will be possible to opt into the CSP
compatible mode using the ngCsp directive. When this mode is on Angular
will evaluate all expressions up to 30% slower than in non-CSP mode, but
no security violations will be raised.

In order to use this feature put ngCsp directive on the root element of
the application. For example:

<!doctype html>
<html ng-app ng-csp>
  ...
  ...
</html>

Closes #893
2012-04-27 23:04:24 -07:00
Igor Minar 2b1b257034 chore(server.js): Add CSP support
The support is disabled by default, uncomment relevant lines to enable
it.
2012-04-27 22:04:16 -07:00
Igor Minar 73caf76225 chore(check-size): add a script to check gziped size
this is useful to quickly check the resulting size during development
2012-04-23 11:42:27 -07:00
Igor Minar dbb92efd13 chore(release): start 1.0.0rc7 rc-generation iteration 2012-04-23 11:42:26 -07:00
Vojta Jina 1214084e9d docs(directive): fix transclusion examples 2012-04-21 21:08:30 +02:00
Misko Hevery a18926f986 fix(events): include ie8 in extra event property reset 2012-04-20 17:04:21 -07:00
Misko Hevery b806b30861 fix(bootstrap): rewritten to $script 2012-04-20 17:04:21 -07:00
Misko Hevery 43d15f830f fix(mouseenter): FF no longer throws exceptions 2012-04-20 17:04:21 -07:00
Igor Minar 1d26acb874 chore(release): cutting the 1.0.0rc6 runny-nose release 2012-04-20 15:06:39 -07:00
Igor Minar 983c309542 docs(changelog): release notes for 1.0.0rc6 runny-nose 2012-04-20 15:06:05 -07:00
Igor Minar 904b69c745 fix(select): properly handle empty & unknown options without ngOptions
Previously only when ngOptions was used, we correctly handled situations
when model was set to an unknown value. With this change, we'll add/remove
extra unknown option or reuse an existing empty option (option with value
set to "") when model is undefined.
2012-04-20 14:29:37 -07:00
Igor Minar c65c34ebfe test(selectSpec): clean up and simplify specs 2012-04-20 14:29:36 -07:00
Igor Minar 8ebe5ccd9a feat(jquery): jquery 1.7.2 support 2012-04-20 14:29:36 -07:00
simpulton e61fd1b43a feat($resource): support HTTP PATCH method
Properly serialize data into request body instead of url.

Closes #887
2012-04-20 12:32:33 -07:00
Misko Hevery ce15a3e049 chore(license): update to google 2012-04-20 11:29:34 -07:00
Misko Hevery 46bb08a9d0 fix(compiler): reading comment throws error in ie
Unders some circumstances reading the comment's text throws error.
2012-04-20 11:29:34 -07:00
Misko Hevery 94dd685709 fix(script): Incorrectly reading script text on ie
IE deals with script tags in special way and .text() does not work. Reading the .text property directly fixes the issue.
2012-04-20 11:29:34 -07:00
Misko Hevery dc32ea627e chore(logo): added angular shield logo 2012-04-20 11:29:33 -07:00
Misko Hevery eafe15f54c fix(document): accidental clobbering of document.getAttribute
Closes #877
2012-04-20 11:29:33 -07:00
Chris Dawson 666f326c5d docs(guide/controllers): update w/ controller scope separation 2012-04-20 10:57:26 -07:00
pkozlowski-opensource 908785960d docs(guide/e2e): fix a link to e2e dsl 2012-04-20 10:49:44 -07:00
johnlindquist 5cc245dd80 docs(ngBind): "angular.module.ng.$sanitize" -> "angular.module.ngSanitize.$sanitize" 2012-04-20 10:44:18 -07:00
johnlindquist 0bd0ef7813 docs($compile) "updateh"->"updated" 2012-04-20 10:43:58 -07:00
johnlindquist 0c7252f929 docs(ngBind): "makes make" -> "makes" 2012-04-20 10:43:41 -07:00
Vojta Jina b94fb5c8c1 docs($resource): fix the example 2012-04-15 09:42:54 -07:00
Igor Minar c322735f83 chore(release): starting the 1.0.0rc6 runny-nose iteration 2012-04-12 03:57:14 -07:00
Igor Minar 9260b4937d chore(release): cutting the 1.0.0rc5 reality-distortion release 2012-04-12 03:56:28 -07:00
Igor Minar e9ccec76a6 docs(changelog): release notes for 1.0.0rc5 reality-distortion 2012-04-12 03:26:10 -07:00
Igor Minar 2037facc99 docs(tutorial): update step-04 to v1.0 2012-04-12 02:45:12 -07:00
Igor Minar b2d0a386f6 style(docs-scenario.html): rename <angular/> to AngularJS in the title 2012-04-12 02:36:03 -07:00
Igor Minar 6d7e7fdea6 fix($location): properly rewrite urls in html5 mode with base url set
previously we were doing all kinds of checks to see if we should rewrite the url or not and we
were missing many scenarios. not any more.

with this change, we rewrite the url unless:
- the href is not set
- link has target attribute
- the absolute url of the link doesn't match the absolute prefix for all urls in our app

This also means that ng-ext-link attribute which we previously used to distinguish external
links from app links is not necessary any more. apps can just set target=_self to prevent
rewriting.

BREAKING CHANGE: ng-ext-link directive was removed because it's unnecessary

apps that relied on ng-ext-link should simply replace it with target=_self
2012-04-12 02:36:03 -07:00
Igor Minar df72852f34 fix(e2eRunner): $browser.location should delegate to apps $location
previously it would create a new instance which wasn't configured as the one in the app,
which resulted in incorrect values being returned in html5 mode with base url set
2012-04-12 02:36:03 -07:00
simpulton c4f6ccb065 docs($compile): fixed typo 2012-04-11 23:48:53 -07:00
Igor Minar 0c49bbdc38 test(ngView): fix failing e2e tests 2012-04-11 21:27:55 -07:00
Igor Minar 7d074a3775 docs($http): fix return types 2012-04-11 17:29:16 -07:00
Igor Minar dceafd32ee feat($http): expose the defaults config as $http.defaults
Often it is impossible to set the http defaults during the config phase,
because the config info is not available at this time.

A good example is authentication - often the app needs to bootstrap,
allow user to enter credentials and only then it gains access to
session token which then should be sent to the server with every request.
Without having the ability to set the defaults at runtime, the developer
either has to resort to hacks, or has to set the session token header
with every request made by the app.
2012-04-11 17:29:16 -07:00
Thibault Leruitte 0a5050eb3c fix($location): don't rewrite links to different base paths
links to different base paths should not be left untouched
2012-04-11 17:27:32 -07:00
Vojta Jina 7c430c5ed0 chore(release scripts): group changelog only if more than 1 entry 2012-04-11 16:12:58 -07:00
Vojta Jina 93d62860e9 fix(input.radio): support 2-way binding in a repeater
Closes #869
2012-04-11 15:50:52 -07:00
Vojta Jina 5bcd719866 chore(ngSanitize): extract $sanitize, ngBindHtml, linkyFilter into a module
Create build for other modules as well (ngResource, ngCookies):
- wrap into a function
- add license
- add version

Breaks `$sanitize` service, `ngBindHtml` directive and `linky` filter were moved to the `ngSanitize` module. 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']);`
2012-04-11 15:50:47 -07:00
Igor Minar e1743cc837 docs($compile): fix typo in the docs templateURL -> templateUrl 2012-04-11 11:23:48 -07:00
Igor Minar 52ee1ab5eb chore(*): remove dead code and fix code style issues 2012-04-10 16:52:12 -07:00
Vojta Jina fcc556df37 docs(guide.forms): fix the forms dev guide to use ng-disabled 2012-04-10 13:42:17 -07:00
Igor Minar 5c0ec9d06d docs(angular.bootstrap): fix typos and errors 2012-04-10 13:21:29 -07:00
Igor Minar ac2f0cece6 docs(tutorial): fix typos in steps 2 and 3 2012-04-10 06:04:13 -07:00
Igor Minar fbaa1968b7 chore($browser): remove the addJs method
this was never meant to be a public api used by apps. I refactored
the code to hide the functionality.

BREAKING CHANGE: $browser.addJs method was removed

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.
2012-04-09 17:59:47 -07:00
Igor Minar 13d5528a5f chore($browser): remove the addCss method
this api was never supposed to be public. nobody should be relying
on it.

I'm removing it since angular doesn't need it.

BREAKING CHANGE: $browser.addCss was removed

apps the depend on this functionality should write a simple utility
function specific to the app (see this diff for hints).
2012-04-09 15:21:46 -07:00
Igor Minar b5406d276d chore(ngBind): remove obsolete test
this test is not testing what it claims it is.

we don't need it any more
2012-04-09 11:49:51 -07:00
Igor Minar 0f89383d98 chore(tests): rename all directive names to the normalized form 2012-04-09 11:48:54 -07:00
Igor Minar 10daefc6f4 fix(ngBindHtml): clear contents when model is falsy
Closes #864
2012-04-09 09:52:28 -07:00
Igor Minar dc7b764d4d test(ngBindSpec): correct tests + split them up 2012-04-09 09:52:28 -07:00
Igor Minar 82d90a4096 fix(docs): change all directive references to use the normalized names 2012-04-09 09:52:27 -07:00
Igor Minar 7468bcb80b chore(release): starting 1.0.0rc5 reality-distortion iteration 2012-04-09 08:59:31 -07:00
Igor Minar bd4a4d390c chore(release): cutting the 1.0.0rc4 insomnia-induction release 2012-04-05 11:46:36 -07:00
Igor Minar 94fca76a08 docs(changelog): release notes for 1.0.0rc4 insomnia-induction 2012-04-05 11:45:34 -07:00
Igor Minar 1c8c083404 fix(docs): move $cookies and $cookieStore docs to module 2012-04-05 11:33:42 -07:00
Igor Minar 0f2de12273 chore(docs): add nonminified jquery debug version of docs 2012-04-05 11:33:42 -07:00
Igor Minar 1bbc67ef6c chore(Rakefile): fix and improve file rewriting code 2012-04-05 10:22:27 -07:00
Igor Minar 637817e3ba fix(Rakefile): move 'use strict'; flag into the angular closure
closure compiler is stubborn and puts the flag to the top of the file, so
we have to post-process the minified file to move the flag into the angular
closure.
2012-04-05 10:22:27 -07:00
Vojta Jina 86182a9415 feat($http): add withCredentials config option 2012-04-04 16:13:02 -07:00
Igor Minar 15ecc6f366 feat($route): allow chaining of whens and otherwise
Previously one had to write:

$routeProvider.when('/foo', {...});
$routeProvider.when('/bar', {...});
$routeProvider.otherwise({...});

After this change it's just:

$routeProvider.
    when('/foo', {...}).
    when('/bar', {...}).
    otherwise({...});

Breaks #when which used to return the route definition object but now
returns self. Returning the route definition object is not very useful
so its likely that nobody ever used it.
2012-04-04 16:10:44 -07:00
Igor Minar 53b2254ea7 docs(tutorial): update tutorial intro + steps 0-3
also contains all kinds of fixes that I had to make in the docs app to
get the tutorial to render correctly
2012-04-04 15:59:18 -07:00
Igor Minar 6336b6e89e chore(docs): restore old tutorial ngdoc files 2012-04-04 15:59:18 -07:00
Igor Minar fdf17d729f fix(docs): remove ngModelInstant from all examples
just fixing leftover code after the removal of ngModelInstant
2012-04-04 15:56:15 -07:00
Vojta Jina 85776c0d37 refactor(ngHref, ngSrc): remove duplicate tests 2012-04-04 15:01:27 -07:00
Vojta Jina 02cf958a07 chore(directive): correct file names for booleanAttrs 2012-04-04 14:58:27 -07:00
Vojta Jina 8fe4295a06 refactor(ngInclude): correct the tests 2012-04-04 14:58:07 -07:00
Vojta Jina dcb8e0767f fix(booleanAttrs): convert to boolean
jQuery's attr() does not handle 0 as false, when it comes to boolean attrs.
2012-04-04 08:26:28 -07:00
Misko Hevery 21b77ad5c2 fix(form): preperly clean up when invalid widget is removed
Removing invalid widget sometimes resulted in improper cleanup of the form state.
2012-04-03 23:28:05 -07:00
Misko Hevery 2f5dba488e fix(ng-href): copy even if no binding
Closes# 850

fixed an issue where ng-href would not copy its content into href if it did not contain binding.
2012-04-03 16:02:20 -07:00
Misko Hevery 7e86eacf30 fix($compile): relax the restriction that directives can not add siblings
Relax the restriction that directives can not add siblings
2012-04-03 16:02:20 -07:00
Vojta Jina 15c1fe3929 refactor(ngView): remove extra $watch, refactor one ugly test 2012-04-03 10:10:44 -07:00
Vojta Jina 428f2b5636 feat(ngInclude): allow ngInclude on css class
And make it terminal so that it does not compile its content, which would cause leaks.
2012-04-03 10:10:44 -07:00
Vojta Jina 199ac26986 fix(ngInclude): fire $includeContentLoaded on proper (child) scope 2012-04-03 10:10:44 -07:00
Vojta Jina 5f70d615a5 refactor(ngInclude): remove scope attribute
The purpose of allowing the scope to be specified was to enable the $route service to work
together with ngInclude. However the functionality of creating scopes was in the recent past
moved from the $route service to the ngView directive, so currently there is no valid use case
for specifying the scope for ngInclude. In fact, allowing the scope to be defined can under
certain circumstances lead to memory leaks.

Breaks ngInclude does not have scope attribute anymore.
2012-04-03 10:10:44 -07:00
Vojta Jina 06d0955074 feat(ngModel): update model on each key stroke (revert ngModelInstant)
It turns out that listening only on "blur" event is not sufficient in many scenarios,
especially when you use form validation you always had to use ngModelnstant
e.g. if you want to disable a button based on valid/invalid form.

The feedback we got from our apps as well as external apps is that the
ngModelInstant should be the default.

In the future we might provide alternative ways of suppressing updates
on each key stroke, but it's not going to be the default behavior.

Apps already using the ngModelInstant can safely remove it from their
templates. Input fields without ngModelInstant directive will start propagating
the input changes into the model on each key stroke.
2012-04-03 10:10:44 -07:00
Vojta Jina a22e0699be feat($sniffer): add hasEvent method for sniffing events
Skip changelog
2012-04-03 10:10:44 -07:00
Brad Green 28ff7c3a66 Doc fixes in bootstrap
Now makes sense.
2012-04-03 07:07:49 -07:00
Mykhailo Kotsur 59ae8adb3c fix(scenario): dev secenario tests
closes #843

Fixed failed e2e test
2012-04-02 16:10:15 -07:00
Igor Minar c0b78478a0 fix($q): $q.reject should forward callbacks if missing
$q.reject('some reason').then() should not blow up, but correctly
forward the callbacks instead.

Closes #845
2012-04-02 10:14:04 -07:00
Mykhailo Kotsur 59fa40ec0e fix($location): search setter should not double-encode the value
By mistake both the setter and helper function that composes the whole
url were encoding the search values.

Closes #751
2012-04-02 08:35:30 -07:00
Igor Minar a1f7f5d4d0 chore(release): start 1.0.0rc4 insomnia-induction iteration 2012-03-30 13:23:36 -07:00
Igor Minar 20687aa5f6 chore(release): cutting 1.0.0rc3 barefoot-telepathy 2012-03-29 16:10:40 -07:00
Igor Minar fc52b81d52 fix(docs): update the example widget regexp for detecting angular.js url
so that we don't show angular-cookies instead of angular.js
2012-03-29 16:10:40 -07:00
Igor Minar ae1aee2b6c fix(FormController): ask for dependency to fool the BC module 2012-03-29 16:10:40 -07:00
Igor Minar 423242017e fix(docs): properly rewrite urls in doc examples at docs-next 2012-03-29 16:10:40 -07:00
Vojta Jina 95c5df5958 fix(ngValue): bind properly inside ng-repeat 2012-03-29 14:05:19 -07:00
Igor Minar 2cb907a836 fix($injector): properly infer dependencies from fn with no args
Previously if there was a white-space in fn: fn( ) {} we failed to infer no args.

This was originally reported by recht, but I decided to use a different fix.

Closes #829
2012-03-29 11:21:04 -07:00
Igor Minar 2f2fd465a4 docs(changelog): release notes for 1.0.0rc3 barefoot-telepathy 2012-03-29 08:10:28 -07:00
Vojta Jina 6da355c3e1 refactor($compile): move methods of attr object into prototype
We have many instances of this object and we clone them as well (e.g. ng-repeat).
This should save some memory and performance as well.

Double prefixed private properties of attr object:
attr.$element -> attr.$$element
attr.$observers -> attr.$$observers

Update shallowCopy to not copy $$ properties and allow passing optional destination object.
2012-03-29 07:30:32 -07:00
Vojta Jina f2106692b1 fix($compile): properly clone attr.$observers in ng-repeat
The `attr` object was only shallow copied which caused all observers to be shared.
Fixing similar issue in ng-* boolean attributes as well as ng-src and ng-href.
2012-03-29 07:30:32 -07:00
Vojta Jina 4557881cf8 chore(release scripts): auto release scripts 2012-03-29 07:22:13 -07:00
Igor Minar af0ad6561c refactor(fromJson/toJson): move the contents of these files into Angular.js
these files are now mostly empty so it doesn't make sense to keep them
separated from other helper functions
2012-03-28 16:57:34 -07:00
Igor Minar 35125d2513 refactor(toJson): use native JSON.stringify
Instead of using our custom serializer we now use the native one and
use the replacer function to customize the serialization to preserve
most of the previous behavior (ignore $ and $$ properties as well
as window, document and scope instances).
2012-03-28 16:57:22 -07:00
Igor Minar 87f5c6e5b7 refactor(fromJson): always use native JSON.parse
This breaks IE7 for which you can use polyfill:

https://github.com/douglascrockford/JSON-js

<!--[if lt IE 8]>
<script src="json2.min.js"></script>
<![endif]-->

or

http://bestiejs.github.com/json3/

<!--[if lt IE 8]>
<script src="json3.min.js"></script>
<![endif]-->
2012-03-28 16:30:38 -07:00
Igor Minar a8a750ab05 feat($http): make the transform defaults to an array
$httpProvider.defaults.transformRequest and $httpProvider.defaults.transformResponse
are now arrays containing single function. This makes it easy to add an
extra transform fn.

adding an extra fn before had to be done in this cluncky way:

$httpProvider.defaults.transformResponse =
[$httpProvider.defaults.transformResponse, myTransformFn];

after this change, it's simply:

$httpProvider.defaults.transformResponse.push(myTransformFn);
2012-03-28 16:30:38 -07:00
Igor Minar 13a95ae499 style($http): remove redundant 'use strict' header 2012-03-28 16:30:31 -07:00
Igor Minar da9f4dfcf4 feat(TzDate): add support for toISOString method 2012-03-28 16:30:31 -07:00
Igor Minar ac4318a2fa refactor(fromJson/date filter): move date string logic to date filter
Breaks angular.fromJson which doesn't deserialize date strings into date objects.

This was done to make fromJson compatible with JSON.parse.

If you do require the old behavior - if at all neeeded then because of
json deserialization of XHR responses - then please create a custom
$http transform:

$httpProvider.defaults.transformResponse.push(function(data) {
  // recursively parse dates from data object here
  // see code removed in this diff for hints
});

Closes #202
2012-03-28 16:30:30 -07:00
Misko Hevery bb2fa6f63f fix(i18n e2e tests): 2012-03-28 11:24:47 -07:00
Igor Minar ba59ef4950 docs(examples): update example apps 2012-03-28 11:24:47 -07:00
Igor Minar 8b93541522 style(Rakefile): use snake_case in ruby code 2012-03-28 11:16:36 -07:00
Misko Hevery 7b22d59b4a chore(ngCookies): moved to module 2012-03-28 11:16:36 -07:00
Misko Hevery 798bca62c6 chore(resource): moved to module 2012-03-28 11:16:36 -07:00
Misko Hevery 8218c4b60b chore(Rakefile): get ready for modules 2012-03-28 11:16:36 -07:00
Misko Hevery 2430f52bb9 chore(module): move files around in preparation for more modules 2012-03-28 11:16:35 -07:00
Brad Green 944098a4e0 Updated manual bootstrap document
Explained why you'd want to manually bootstrap, added contrasting
example for automatic vs manual methods.
2012-03-27 18:06:00 -07:00
Brad Green 2ce0485e6f Rewrite of Automatic Initialization doc
Added examples, explained the reasons why you initialize the whole app
or parts of the page.
2012-03-27 08:28:34 -07:00
Vojta Jina a08cbc02e7 feat($compile): do not interpolate boolean attributes, rather evaluate them
So that we can have non string values, e.g. ng-value="true" for radio inputs

Breaks boolean attrs are evaluated rather than interpolated

To migrate your code, change: <input ng-disabled="{{someBooleanVariable}}">
to: <input ng-disabled="someBooleanVariabla">


Affected directives:

* ng-multiple
* ng-selected
* ng-checked
* ng-disabled
* ng-readonly
* ng-required
2012-03-26 21:14:09 -07:00
Vojta Jina 55027132f3 refactor(ngBindAttr): remove
Breaks ng-bind-attr directive removed
2012-03-26 21:14:09 -07:00
Vojta Jina 09e175f02c feat(ngValue): allow radio inputs to have non string values
Closes #816
2012-03-26 21:14:09 -07:00
Mykhailo Kotsur 5c5b1183c8 docs(guide/module): fix syntax error and expectation in test example 2012-03-26 16:06:46 -07:00
Mykhailo Kotsur f04142ea28 docs(guide/unit-testing): fixed typo in code example 2012-03-26 16:06:16 -07:00
Igor Minar aaedefb92e refactor($sniffer): make $sniffer service private
This service has been accidentaly documented in the past, it should not be considered
to be public api.

I'm also removing fallback to Modernizr since we don't need it.

Breaks any app that depends on this service and its fallback to Modernizr, please
migrate to custom "Modernizr" service:

    module.value('Modernizr', function() { return Modernizr; });
2012-03-26 15:43:59 -07:00
Igor Minar d54dfecb00 feat($controller): support controller registration via $controllerProvider
It's now possible to register controllers as:

.register('MyCtrl', function($scope) { ... });
// or
.register('MyCtrl', ['$scope', function($scope) { ... });

Additionally a module loader shortcut api was added as well:

myModule.controller('MyCtr', function($scope) { ... });
2012-03-26 15:23:29 -07:00
Igor Minar 4b8d926062 feat(assertArgFn): should support array annotated fns 2012-03-26 12:21:42 -07:00
Igor Minar 74c84501ed doc(guide/module): fix typo 2012-03-23 16:57:24 -07:00
Igor Minar 4581b79bbd doc(guide/controller): fix examples 2012-03-23 16:54:48 -07:00
Manuel Woelker 2be8847ef6 doc(guide): order topic list in guide sidebar in accordance with overview
Closes #405
2012-03-23 16:31:33 -07:00
Igor Minar cb2ad9abf2 fix(init): use jQuery#ready for init if available
Closes #818
2012-03-23 15:41:37 -07:00
Misko Hevery 73c8593077 feat(http): added params parameter
The params parameter can now be used to serialize parameters in the URLs. The serialization does proper escaping and JSON encoding if it is an object.
2012-03-23 14:21:43 -07:00
Misko Hevery ac75079e21 fix(q): resolve all of nothing to nothing
$q.all([]) no longer throws exception and resolves to empty array []
2012-03-23 14:21:43 -07:00
Igor Minar 5390fb37d2 fix($compile): create new (isolate) scopes for directives on root elements
previously we would not create them and it's causing all kinds of issues and accidental leaks

Closes #817
2012-03-23 11:46:54 -07:00
Igor Minar 8d7e694849 fix(forEach): should ignore prototypically inherited properties
Closes #813
2012-03-22 16:39:36 -07:00
Igor Minar 5fdab52dd7 feat(jqLite): make injector() and scope() work with the document object
For typical app that has ng-app directive on the html element, we now can do:

angular.element(document).injector() or .injector()
angular.element(document).scope() or .scope()

instead of:

angular.element(document.getElementsByTagName('html')[0]).injector()
...
2012-03-22 16:39:36 -07:00
Vojta Jina 541bedd1a9 refactor(ngController): remove unused deps 2012-03-22 16:29:31 -07:00
Igor Minar 98e18a64aa docs(cookbook/form): fix the example
Closes #712
2012-03-21 13:52:11 -07:00
Igor Minar 0a45bff472 chore(docs): switch disqus id from angularjs to angularjs-next 2012-03-21 13:46:35 -07:00
Igor Minar 263524d381 docs(changelog): fix rc2 release date 2012-03-20 17:21:41 -07:00
Igor Minar 52c59cf0ce chore(release): start 1.0.0rc barefoot-telepathy iteration 2012-03-20 16:02:49 -07:00
Igor Minar c5f8edfe03 chore(release): cutting the 1.0.0rc2 silence-absorption release 2012-03-20 15:38:57 -07:00
Igor Minar 69f0aa899d docs(changelog): release notes for 1.0.0rc2 silence-absorption 2012-03-20 15:25:31 -07:00
Daniel Zen e7cd0bcc5a docs(guide/controllers): add a section on testing controllers 2012-03-20 15:23:58 -07:00
Vojta Jina ade6c45275 feat(input.radio): Allow value attribute to be interpolated 2012-03-20 14:39:23 -07:00
Igor Minar 9eafd10fcd docs(guide/location): fix example 2012-03-20 12:05:57 -07:00
Igor Minar 3436c027f2 docs(guide/started): fix examples 2012-03-20 11:30:21 -07:00
Igor Minar 6a8749e65a refactor($resource): unify and simplify the code 2012-03-20 11:07:38 -07:00
Igor Minar 1a5bebd927 fix($http): don't send Content-Type header when no data
When a http request has no data (body), we should not send the
Content-Type header as it causes problems for some server-side
frameworks.

Closes #749
2012-03-20 11:07:38 -07:00
Igor Minar 83155e8fbe style(ResourceSpec): style clean up 2012-03-20 11:07:37 -07:00
Igor Minar 6d6f875345 fix($resource): support escaping of ':' in resource url
So one can how define cors/jsonp resources with port number as:

resource.route('http://localhost\\:8080/Path')
2012-03-20 11:07:37 -07:00
Igor Minar a4fe51da3b feat($route): when matching consider trailing slash as optional
This makes for a much more flexible route matching:

- route /foo matches /foo and redirects /foo/ to /foo
- route /bar/ matches /bar/ and redirects /bar to /bar/

Closes #784
2012-03-20 11:07:37 -07:00
Igor Minar ee5a5352fd fix(e2e runner): fix typo that caused errors on IE8
Closes #806
2012-03-20 11:07:37 -07:00
Igor Minar 9cb2195e61 fix($compile): don't touch static element attributes
Compiler should not reassign values to element attributes if its not neccessary due
to interpolation or special attribute magic (ng-src -> src)

This resolves several issues on IE caused by reassigning script.src attribute which
caused all of the scripts to be reloaded.
2012-03-20 11:07:36 -07:00
Igor Minar 15213ec212 fix($log): avoid console.log.apply calls in IE
In IE window.console.log and friends are functions that don't have apply or call fns.

For this reason we have to treat them specially and do our best to log at least
something when running in this browser.

Closes #805
2012-03-20 11:07:36 -07:00
Igor Minar 9171c76bb4 style($log): reformat code for readability 2012-03-20 11:07:35 -07:00
Igor Minar 64fb1f2620 docs(filters): use ng-model-instant in live examples
Closes #807
2012-03-20 11:07:35 -07:00
Vojta Jina f49eaf8bf2 fix($compile): Merge interpolated css class when replacing an element 2012-03-20 10:39:43 -07:00
Vojta Jina f701ce08f9 fix(matchers.toHaveClass): Correct reference to angular.mock.dump 2012-03-19 17:26:29 -07:00
Misko Hevery 1cc0e4173d bug(ie7): incorrectly set all inputs to disabled
In ie7 all of the input fields are set to readonly and disabled, because ie7 enumerates over all attributes even if the are not declared on the element.
2012-03-19 15:49:42 -07:00
Misko Hevery d4ae7988da chore(parseInt): cleanup parseInt() for our int() 2012-03-19 11:41:23 -07:00
Misko Hevery 5ac14f633a fix(json): added support for iso8061 timezone
Added support of timezone in dates not just zulu timezone.

This fixes issues for date filter which uses json deserialization under the hood. (for now)

Closes #/800
2012-03-19 11:41:10 -07:00
Misko Hevery 9918b748be fix(compiler): allow transclusion of root elements
Fixed an issue where a directive that uses transclusion (such as ngRepeat) failed to link if it was declared on the root element of the compilation tree. (For example ngView or ngInclude including template where ngRepeat was the top most element).
2012-03-19 11:35:10 -07:00
Misko Hevery 6ecac8e71a fix(select): multiselect failes to update view on selection insert
In multiselect when the underlying selection array push/pops an element the view did not re-render since the array reference stayed the same.
2012-03-19 11:35:10 -07:00
Misko Hevery 823adb2319 fix(ngForm): alias name||ngForm
form directive was requiring name attribute even when invoked as attribute, resulting in unnecessary duplication
2012-03-19 11:35:09 -07:00
Misko Hevery 21e74c2d2e fix(ngView): controller not published
corrected omitted assignment of controller to the element data object. Without this fix the controller created by ngView is not accessible from the browser debugger.
2012-03-19 11:35:09 -07:00
Misko Hevery 6c5a05ad49 feat(jqLite): add .controller() method
extend JQuery with .controller() method which retrieves the closest controller for a given element
2012-03-19 11:35:09 -07:00
Vojta Jina 192ff61f5d feat(scope.$eval): Allow passing locals to the expression 2012-03-18 23:46:30 -07:00
Igor Minar 935c1018da fix(ngRepeat): correct variable reference in error message
Closese #803
2012-03-17 15:57:55 -07:00
Igor Minar 78a6291666 docs(scope): add $destroy event docs 2012-03-16 15:32:14 -07:00
Igor Minar 53b6f522a5 fix(ngDocSpec): fix broken tests 2012-03-16 15:32:14 -07:00
442 changed files with 11152 additions and 10427 deletions
+296 -9
View File
@@ -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 -1
View File
@@ -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
+129 -146
View File
@@ -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
+150 -113
View File
@@ -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;
Executable
+205
View File
@@ -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]);
}
+43
View File
@@ -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']);
});
});
});
+80
View File
@@ -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))
Executable
+5
View File
@@ -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/>
&ltspan ng:bind="name"&gt <span ng:bind="name"></span> <br/>
&ltspan ng_bind="name"&gt <span ng_bind="name"></span> <br/>
&ltspan ng-bind="name"&gt <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>
+2 -2
View File
@@ -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 -5
View File
@@ -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.
+11 -11
View File
@@ -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
+10 -10
View File
@@ -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
+4 -3
View File
@@ -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
+18 -22
View File
@@ -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.
+1 -1
View File
@@ -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.
+4 -4
View File
@@ -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.
+1 -1
View File
@@ -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: `&lt;a href="/some?foo=bar"&gt;link&lt;/a&gt;`
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 &lt;base href="" /&gt; !**
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`.
+2 -2
View File
@@ -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>
+3 -3
View File
@@ -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!');
});
});
});
+4 -4
View File
@@ -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:
+148
View File
@@ -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>}
+275
View File
@@ -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>
+57
View File
@@ -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>
+212
View File
@@ -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/ModelViewController the Model-View-Controller (MVC)
design pattern} to decouple the code and to separate concerns. With that in mind, let's use a
little Angular and JavaScript to add model, view, and controller components to our app.
<doc:tutorial-instructions step="2"></doc:tutorial-instructions>
The app now contains a list with 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>
+196
View File
@@ -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>
+194
View File
@@ -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>
+238
View File
@@ -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>
+106
View File
@@ -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>
+261
View File
@@ -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>
+197
View File
@@ -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>
+143
View File
@@ -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>
+141
View File
@@ -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>
+223
View File
@@ -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>
+21
View File
@@ -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}.
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

+1 -1
View File
@@ -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');
});
+1
View File
@@ -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{'), ';']));
+20 -5
View File
@@ -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();
});
}
+5 -5
View File
@@ -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');
+2 -2
View File
@@ -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>&lt;angular/&gt; 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>
+21
View File
@@ -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;
}
+7 -6
View File
@@ -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(){
+11 -5
View File
@@ -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)">
+1
View File
@@ -0,0 +1 @@
../../../lib/jquery/jquery.js
+15 -12
View File
@@ -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 -15
View File
@@ -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;
})();
+20 -19
View File
@@ -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();
}));
});
});
+17 -9
View File
@@ -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 -1
View File
@@ -1,2 +1,2 @@
view1<br>
location: {{$service('$location').href}}
location: {{url()}}
+1 -1
View File
@@ -1,2 +1,2 @@
view2<br/>
location: {{$service('$location').href}}<br/>
location: {{url()}}<br/>
+4 -2
View File
@@ -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- ');
});
}
+32 -32
View File
@@ -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('€&nbsp;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&nbsp;234&nbsp;443&nbsp;432');
expect(binding('input | currency')).toBe('234&nbsp;234&nbsp;443&nbsp;432,00&nbsp;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&nbsp;€');
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&nbsp;234&nbsp;443&nbsp;432');
expect(binding('input | currency')).toBe('234&nbsp;234&nbsp;443&nbsp;432,00&nbsp;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('彭迪 和 一哥 在浏览该文件!');
});
})
});
+4 -4
View File
@@ -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>
+4 -4
View File
@@ -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>
+16 -16
View File
@@ -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>
+4 -4
View File
@@ -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>
+8 -8
View File
@@ -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>
+16 -16
View File
@@ -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>
+1 -1
View File
@@ -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();
File diff suppressed because it is too large Load Diff
@@ -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 &lt;li ng:repeat="phone in phones"&gt;\
\f0\b\fs20 \cf0 &lt;li ng-repeat="phone in phones"&gt;\
\{\{phone.name\}\}\
&lt;p&gt;\{\{phone.snippet\}\}&lt;/p&gt;\
&lt;/li&gt;}</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 &lt;body \
ng:controller=\
ng-controller=\
"PhoneListCtrl"&gt;}</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 &lt;input name="query"&gt;}</string>
\f0\b\fs20 \cf0 &lt;input ng-model="query"&gt;}</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 &lt;li ng:repeat="phone in phones"&gt;\
\f0\fs20 \cf0 &lt;li ng-repeat="phone in phones"&gt;\
\{\{phone.name\}\}\
&lt;p&gt;\{\{phone.snippet\}\}&lt;/p&gt;\
&lt;/li&gt;}</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 &lt;body \
ng:controller=\
ng-controller=\
"PhoneListCtrl"&gt;}</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>
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+903 -799
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
File diff suppressed because one or more lines are too long

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