Compare commits

...

214 Commits

Author SHA1 Message Date
Igor Minar 5fdf42ce39 chore(release): cut the 1.0.0rc10 tesseract-giftwrapping release 2012-05-23 21:05:21 -07:00
Igor Minar bf6a0b7289 docs(changelog): release notes for 1.0.0rc10 tesseract-giftwrapping 2012-05-23 16:37:37 -07:00
Misko Hevery 989446ecee fix($rootScope): TTL exception does not clear $$phase
When $digest() throws infinite digest exception it
does not properly clear the $phase leaving the scope
in an inconsistent state.

Closes #979
2012-05-23 16:01:20 -07:00
Igor Minar 5214c1d0cb chore(package.json): add simple package.json with npm dependencies 2012-05-23 15:00:56 -07:00
Igor Minar 4511d39cc7 feat($timeout): add $timeout service that supersedes $defer
$timeout has a better name ($defer got often confused with something related to $q) and
is actually promise based with cancelation support.

With this commit the $defer service is deprecated and will be removed before 1.0.

Closes #704, #532
2012-05-23 15:00:56 -07:00
Daniel Gomes 15b8f205bb docs($filter): minor corrections 2012-05-22 14:27:26 -07:00
Max 1d388676e3 fix(ngRepeat): expose $first, $middle and $last instead of $position
$position marker doesn't work well in cases when we have just one item
in the list because then the item is both the first and last. To solve
this properly we need to expose individual $first and $middle and $last
flags.

BREAKING CHANGE: $position is not exposed in repeater scopes any more

To update, search for $position and replace it with one of $first,
$middle or $last.

Closes #912
2012-05-22 14:18:15 -07:00
Vojta Jina 84542d2431 feat(scope): add event.preventDefault() and event.defaultPrevented 2012-05-17 15:47:53 -07:00
Vojta Jina 91db99208e refactor(scope.$emit): rename event.cancel() to event.stopPropagation()
Breaks event.cancel() is event.stopPropagation()
2012-05-17 15:47:52 -07:00
Misko Hevery acf095d178 fix(jqLite): have same expando format as jQuery 2012-05-17 10:36:45 -07:00
Igor Minar 301d8f233b chore(release): start 1.0.0rc10 tesseract-giftwrapping iteration 2012-05-15 00:09:08 -07:00
Igor Minar d70223e53e chore(release): cutting 1.0.0rc9 eggplant-teleportation 2012-05-14 22:13:15 -07:00
Igor Minar 8ad02bb5a8 docs(changelog): release notes for 1.0.0rc9 eggplant-teleportation 2012-05-14 22:00:18 -07:00
Misko Hevery ec1c5dfaee fix(jqLite): .data()/.bind() memory leak
Since angular attaches scope/injector/controller
into DOM it should clean up after itself. No need
to complain about memory leaks, since they can
only happened on detached DOM. Detached DOM would
only be in tests, since in production the DOM
would be attached to render tree and removal
would automatically clear memory.
2012-05-14 21:56:22 -07:00
Misko Hevery 24e7da4f19 fix(angular-mocks): memory leak in jasmine's DI utils
When using inject/module helper methods in tests, these methods would
leave the injector laying around after the test. Since injector is
the application it can grow very large.
2012-05-14 21:56:22 -07:00
Vojta Jina 7b739c9702 fix($sniffer): report history false on Android < 4
Android has history.pushState, but it does not update the location correctly:
http://code.google.com/p/android/issues/detail?id=17471

Closes #904
2012-05-14 15:12:51 -07:00
Igor Minar c1533ef576 fix($location): support urls with any protocol
The url used for location parsing was quite strict and did not support
custom url schemes like "chrome-extension://". With this change the only
requirement for scheme is that it doesn't contain ":" character.
2012-05-14 14:45:56 -07:00
Igor Minar 679cb8a74a fix($browser/$location): single quote in url causes infinite digest in FF
The real issue is in FF, see https://bugzilla.mozilla.org/show_bug.cgi?id=407172.

FF overly encodes stuff which breaks our expectations and then we fail .url() != currentUrl.absUrl()
comparison unexpectidly, which leads to infinite digest.

The workaround is to correct for this inconsistency in $browser and decode any single quotes in urls.

Closes #920
2012-05-13 21:53:19 -07:00
Igor Minar 4e65635f85 doc($rootScope): fix $digest example 2012-05-08 17:00:25 -07:00
Misko Hevery aa02534865 bug(ie8 docs): docs now work on ie8 2012-05-07 15:43:09 -07:00
Misko Hevery b99f65f64d bug(html5 navigation): broken in Opera
http://my.opera.com/community/forums/topic.dml?id=1185462

Closes# 938
2012-05-07 15:43:09 -07:00
Igor Minar f76474823a chore(release): starting 1.0.0rc9 eggplant-teleportation interation 2012-05-07 00:11:50 -07:00
Igor Minar 8ba1fd87e1 chore(release): cutting the 1.0.0rc8 blooming-touch release 2012-05-07 00:09:20 -07:00
Igor Minar 4d2dd46483 docs(changelog): release notes for 1.0.0rc8 blooming-touch 2012-05-07 00:08:43 -07:00
Igor Minar b24cc63bcb fix(ngSrc,ngHref): binding should set element prop as well as attr
IE9 ignores setAttribute('src', val) calls on img if "ng:src" attribute
is present. It only fetches the image if element property is updated as well.

Closes #935
2012-05-06 23:01:33 -07:00
Igor Minar 49dfdf8f02 fix(ngModel): use keydown/change events on IE9 instead of input
On IE9 the input event is not fired when backspace or delete key are pressed or when
cut is performed. This makes listening on the input event unreliable and therefore
it's better for us to just use keydown/change events instead.

Closes #879
2012-05-06 23:01:33 -07:00
Vojta Jina 5bcb749abb fix(scenario): make browser().location() working if ng-app on other than <html> 2012-05-05 03:30:28 +02:00
Vojta Jina 499a76a08c fix($parse): support methods on falsy primitive types
e.g. zero, false, empty string

- fix tests to be executed with csp true
- fix cps (when more than 5 parts)
2012-05-05 03:30:19 +02:00
Misko Hevery 8e2675029f chore(docs): re-skin main documentation 2012-05-04 16:12:17 -07:00
Misko Hevery d0159454df bug($cookie): set on app base path rather the current path. 2012-05-04 15:50:39 -07:00
Misko Hevery 7f0eb15161 fix($compile): have $observe return registration function 2012-05-04 15:50:39 -07:00
Misko Hevery c4fa487250 feat(bootstrap): support code prettify and dropdown from bootstrap 2012-05-04 15:50:37 -07:00
Misko Hevery cef3535c16 chore(controller): allow setting map of controllers 2012-05-04 15:50:37 -07:00
Misko Hevery fbb499e0a8 chore(module): improved module prefix/suffix code 2012-05-04 15:50:37 -07:00
Misko Hevery e40f8d829f chore(debug): rewrite angular-bootstrap.js to use $script 2012-05-04 15:50:37 -07:00
Igor Minar 9c0418cf1a fix($compile): ignore ws when checking if template has single root
Also add the same error checking for sync templates.

Closes #910
2012-05-04 13:01:55 -07:00
Igor Minar 1564b82b49 style($compile): rename orig*Node to beforeTemplate*Node 2012-05-03 23:40:43 -07:00
Igor Minar b431ee3850 fix($compile): fix replaceWith
the old implementation didn't reattach jquery/jqlite data which caused
things like  to be lost

I tried various implementations but it appears that by reattaching the data
to the new node by copying the expando property is the most reliable of all.
2012-05-03 23:40:43 -07:00
Igor Minar a44d3dcd6a chore(testabilityPatch): print number of leaked references if any 2012-05-03 23:31:28 -07:00
Igor Minar ee579a071a feat(jqLite): support data() getter and data(obj) setter
... just like jquery does
2012-05-03 23:31:28 -07:00
Igor Minar 5df7e6fae5 style(jqLite): clean up the code 2012-05-03 23:31:28 -07:00
Igor Minar fff31d8d61 style($compile): clean up the code and normalize fn names 2012-05-03 23:31:28 -07:00
Igor Minar 9cba23a588 chore(trace): add helper method trace
use it as trace('label') to dump the stack during debugging
2012-05-03 10:07:30 -07:00
Igor Minar 705f4bbf11 fix($compile): attach scope to the directive element when templateUrl and replace=true
We forgot to reattach the scope to the replacement element. This affected only
directives that had templateUrl and replace:true properties.

Reported on the mailing list:
https://groups.google.com/forum/?fromgroups#!topic/angular/zwjLr1msS2Y
http://jsfiddle.net/lukebayes/g9Sh9/
2012-05-03 00:15:26 -07:00
Igor Minar bd530e2257 chore($compile): remove obsolete <<CONTENT>> transclusion
This stuff was never documented and is an accidental leftover from the time
when the compiler was rewritten.

If any code depends on this, it should be rewritten to use ngTransclude directive
intead.
2012-05-03 00:15:26 -07:00
Igor Minar 843f762c57 fix($compile): prevent duplicate directive controller instantiation
Closes #876
2012-05-03 00:15:26 -07:00
Igor Minar beea3a4bed style($compile): rename compiler.js to compile.js 2012-05-02 16:37:48 -07:00
Igor Minar 3bd3cc571d fix(select): don't interfere with selection if not databound
Closes #926
2012-05-02 14:24:43 -07:00
Igor Minar c7f1101520 chore(release): starting the 1.0.0rc8 blooming-touch iteration 2012-05-02 14:21:30 -07:00
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
493 changed files with 21698 additions and 13524 deletions
+413 -9
View File
@@ -1,3 +1,407 @@
<a name="1.0.0rc10"></a>
# 1.0.0rc10 tesseract-giftwrapping (2012-05-23)
## Features
- **$timeout:** add `$timeout` service that supersedes `$defer`
([4511d39c](https://github.com/angular/angular.js/commit/4511d39cc748288df70bdc258f98a8f36652e683),
[#704](https://github.com/angular/angular.js/issues/704),
[#532](https://github.com/angular/angular.js/issues/532))
- **scope:** add `event.preventDefault()` and `event.defaultPrevented`
([84542d24](https://github.com/angular/angular.js/commit/84542d2431d20de42d6ec27c9d3435dd72dbe2ee))
## Bug Fixes
- **ngRepeat:** expose `$first`, `$middle` and `$last` instead of `$position`
([1d388676](https://github.com/angular/angular.js/commit/1d388676e3b97b6171fc498e82545bd437ee6fd1),
[#912](https://github.com/angular/angular.js/issues/912))
- **jqLite:** use the same expando store structure as jQuery
([acf095d1](https://github.com/angular/angular.js/commit/acf095d1783e30e750d046ef24e81b5a0a31fbd4))
- **$rootScope:** infinite digest exception does not clear $$phase
([5989a1ed](https://github.com/angular/angular.js/commit/5989a1eda2b9e289b467ef9741fb1476549c8fd9),
[#979](https://github.com/angular/angular.js/issues/979))
## Breaking Changes
- **ngRepeat - `$position` is not exposed in repeater scopes any more**
To update, search for `/\$position/` and replace it with one of `$first`, `$middle` or `$last`.
([1d388676](https://github.com/angular/angular.js/commit/1d388676e3b97b6171fc498e82545bd437ee6fd1))
- **scope event's `cancel` method was renamed to `stopPropagation`**
The name was corrected in order to align better with DOM terminology.
To update, search for `/\.\s*cancel\s*(/` and replace it with `.stopPropagation(` or
`.preventDefault(` (or both) depending on what you actually need.
([91db9920](https://github.com/angular/angular.js/commit/91db99208e197a73584a88a8d835eeb55c466335))
## Deprecation Warnings
- **`$defer` service has been deprecated in favor of `$timeout` service**
The `$defer` service will be removed before 1.0 final, so please migrate your code.
([4511d39c](https://github.com/angular/angular.js/commit/4511d39cc748288df70bdc258f98a8f36652e683))
<a name="1.0.0rc9"></a>
# 1.0.0rc9 eggplant-teleportation (2012-05-14)
## Bug Fixes
- **$location:**
- single quote in url causes infinite digest in FF
([679cb8a7](https://github.com/angular/angular.js/commit/679cb8a74a684454fe38fa9e1ddad396bb598c52),
[#920](https://github.com/angular/angular.js/issues/920))
- support urls with any protocol
([c1533ef5](https://github.com/angular/angular.js/commit/c1533ef5762199bea18d3bf3bcba7fcf89272931))
- don't use buggy history.pushState api on Android < 4
([7b739c97](https://github.com/angular/angular.js/commit/7b739c97028be2a5d5aef679ef1f8064cd10d386),
[#904](https://github.com/angular/angular.js/issues/904))
- work around Opera's base href issue
([b99f65f6](https://github.com/angular/angular.js/commit/b99f65f64d1e54315b3210d78a9a9adbcf34c96c),
[#938](https://github.com/angular/angular.js/issues/938))
- **docs app:** get docs app to work on IE8
([aa025348](https://github.com/angular/angular.js/commit/aa02534865c8e43dcef9e218b12c8c717c837205))
<a name="1.0.0rc8"></a>
# 1.0.0rc8 blooming-touch (2012-05-06)
## Features
- **jqLite:** support data() getter and data(obj) setter
([ee579a07](https://github.com/angular/angular.js/commit/ee579a071a91cbade729d3cb97e097568e71f8fc))
## Bug Fixes
- **$compile:**
- have $observe return registration function
([7f0eb151](https://github.com/angular/angular.js/commit/7f0eb1516165fcb73f1c9953018b7c9b70acfae1))
- ignore ws when checking if template has single root
([9c0418cf](https://github.com/angular/angular.js/commit/9c0418cf1abd609bf0ffbe71fbdfa75905cf8e0f),
[#910](https://github.com/angular/angular.js/issues/910))
- fix replaceWith
([b431ee38](https://github.com/angular/angular.js/commit/b431ee38509724ba9098a7be7a8d6c5dcded4fe9))
- attach scope to the directive element when templateUrl and replace=true
([705f4bbf](https://github.com/angular/angular.js/commit/705f4bbf115d2408e33b25f56edbf1f383aabb82))
- prevent duplicate directive controller instantiation
([843f762c](https://github.com/angular/angular.js/commit/843f762c573e38a044f920c5575c6feb46bc7226),
[#876](https://github.com/angular/angular.js/issues/876))
- **$parse:** support methods on falsy primitive types
([499a76a0](https://github.com/angular/angular.js/commit/499a76a08cc7a7604dab5e1dd9cca675b8e29333))
- **ngModel:** use keydown/change events on IE9 instead of input
([49dfdf8f](https://github.com/angular/angular.js/commit/49dfdf8f0238ef8c473fcb44694f6b5696ecde70),
[#879](https://github.com/angular/angular.js/issues/879))
- **ngSrc,ngHref:** binding should set element prop as well as attr
([b24cc63b](https://github.com/angular/angular.js/commit/b24cc63bcbd45741d21757653f05d54db09e0f20),
[#935](https://github.com/angular/angular.js/issues/935))
- **scenario:** make browser().location() working if ng-app on other than <html>
([5bcb749a](https://github.com/angular/angular.js/commit/5bcb749abb91dba0847cb9bc900777a67fd55aa8))
- **select:** don't interfere with selection if not databound
([3bd3cc57](https://github.com/angular/angular.js/commit/3bd3cc571dcd721f9d71f971aefee23115a5e458),
[#926](https://github.com/angular/angular.js/issues/926))
## Docs
- Brand new bootstrap-based skin for api docs: <http://docs.angularjs.org/>
<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 +467,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 +618,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
+136 -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,51 @@ 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'])
concat_module('bootstrap', ['src/bootstrap/bootstrap.js'])
concat_module('bootstrap-prettify', ['src/bootstrap/bootstrap-prettify.js',
'src/bootstrap/google-prettify/prettify.js'],
gen_css('src/bootstrap/google-prettify/prettify.css', true))
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')
closure_compile('angular-bootstrap.js')
closure_compile('angular-bootstrap-prettify.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 +126,62 @@ 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-bootstrap.js'),
path_to('angular-bootstrap.min.js'),
path_to('angular-bootstrap-prettify.js'),
path_to('angular-bootstrap-prettify.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.gsub!(/'angular(.*)\.js/, '\'angular\1-' + 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 +268,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, footer='')
concat_file('angular-' + name + '.js', ['src/module.prefix'] + files + ['src/module.suffix'], footer)
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
+154 -113
View File
@@ -3,81 +3,112 @@ 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/compile.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/timeout.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',
'src/bootstrap/bootstrap.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/bootstrap/*.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 +117,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 +139,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 +180,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.*
+3 -59
View File
@@ -2,62 +2,6 @@
@name API Reference
@description
## Angular Compiler API
* {@link angular.module.ng.$compileProvider.directive Directives} - Angular DOM element attributes
* {@link angular.module.ng.$filter Filters} - Angular output filters
* {@link angular.module.ng.$compile $compile} - Template compiler
## Angular Scope API
* {@link angular.module.ng.$rootScope.Scope Scope Object} - Angular scope object
## Angular Services & Dependency Injection API
* {@link angular.module.ng Angular Services}
* {@link angular.injector angular.injector() }
## Angular Testing API
* {@link angular.module.ngMock Testing Mocks API} - Mock objects for testing
* {@link guide/dev_guide.e2e-testing Angular Scenario Runner} - Automated scenario testing
documentation
## Angular Utility Functions
### HTML & DOM Manipulation
* {@link angular.element angular.element()}
### Misc
* {@link angular.bind angular.bind() }
* {@link angular.extend angular.extend() }
* {@link angular.forEach angular.forEach() }
* {@link angular.identity angular.identity() }
* {@link angular.noop angular.noop() }
## Type Identification
* {@link angular.isArray angular.isArray() }
* {@link angular.isDate angular.isDate() }
* {@link angular.isDefined angular.isDefined() }
* {@link angular.isFunction angular.isFunction() }
* {@link angular.isNumber angular.isNumber() }
* {@link angular.isObject angular.isObject() }
* {@link angular.isString angular.isString() }
* {@link angular.isUndefined angular.isUndefined() }
## Strings
* {@link angular.lowercase angular.lowercase() }
* {@link angular.uppercase angular.uppercase() }
### JSON
* {@link angular.fromJson angular.fromJson() }
* {@link angular.toJson angular.toJson() }
Use the API Refference documentation when you need more information about a specific feature. Check out
{@link guide/ Developer Guide} for AngularJS concepts. If you are new to AngularJS we recomend the
{@link tutorial/ Tutorial}.
+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/>
+90 -51
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
@@ -30,72 +30,111 @@ In this example we have a simple app which consist of two screens:
* Welcome: url `welcome` Show the user contact information.
* Settings: url `settings` Show an edit screen for user contact information.
<example module="deepLinking" deps="angular-sanitize.js">
<file name="script.js">
angular.module('deepLinking', ['ngSanitize'])
.config(function($routeProvider) {
$routeProvider.
when("/welcome", {template:'welcome.html', controller:WelcomeCntl}).
when("/settings", {template:'settings.html', controller:SettingsCntl});
});
The two partials are defined in the following URLs:
AppCntl.$inject = ['$scope', '$route']
function AppCntl($scope, $route) {
$scope.$route = $route;
* <a href="./examples/settings.html" ng-ext-link>./examples/settings.html</a>
* <a href="./examples/welcome.html" ng-ext-link>./examples/welcome.html</a>
// initialize the model to something useful
$scope.person = {
name:'anonymous',
contacts:[{type:'email', url:'anonymous@example.com'}]
};
}
<doc:example module="deepLinking">
<doc:source jsfiddle="false">
<script>
angular.module('deepLinking', [])
.config(function($routeProvider) {
$routeProvider.when("/welcome", {template:'./examples/welcome.html', controller:WelcomeCntl});
$routeProvider.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
});
function WelcomeCntl($scope) {
$scope.greet = function() {
alert("Hello " + $scope.person.name);
};
}
AppCntl.$inject = ['$scope', '$route']
function AppCntl($scope, $route) {
// initialize the model to something useful
$scope.person = {
name:'anonymous',
contacts:[{type:'email', url:'anonymous@example.com'}]
};
}
function SettingsCntl($scope, $location) {
$scope.cancel = function() {
$scope.form = angular.copy($scope.person);
};
function WelcomeCntl($scope) {
$scope.greet = function() {
alert("Hello " + $scope.person.name);
};
}
$scope.save = function() {
angular.copy($scope.form, $scope.person);
$location.path('/welcome');
};
function SettingsCntl($scope, $location) {
$scope.cancel = function() {
$scope.form = angular.copy($scope.person);
};
$scope.cancel();
}
</file>
<file name="style.css">
[ng-view] {
border: 1px solid blue;
margin: 0;
padding:1em;
}
$scope.save = function() {
angular.copy($scope.form, $scope.person);
$location.path('/welcome');
};
$scope.cancel();
}
</script>
.partial-info {
background-color: blue;
color: white;
padding: 3px;
}
</file>
<file name="index.html">
<div ng-controller="AppCntl">
<h1>Your App Chrome</h1>
[ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
<hr/>
<span style="background-color: blue; color: white; padding: 3px;">
<span class="partial-info">
Partial: {{$route.current.template}}
</span>
<ng:view style="border: 1px solid blue; margin: 0; display:block; padding:1em;"></ng:view>
<div ng-view></div>
<small>Your app footer </small>
</div>
</doc:source>
<doc:scenario>
</file>
<file name="settings.html">
<label>Name:</label>
<input type="text" ng:model="form.name" required>
<div ng:repeat="contact in form.contacts">
<select ng:model="contact.type">
<option>url</option>
<option>email</option>
<option>phone</option>
</select>
<input type="text" ng:model="contact.url">
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<div>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
</div>
<button ng:click="cancel()">Cancel</button>
<button ng:click="save()">Save</button>
</file>
<file name="welcome.html">
Hello {{person.name}},
<div>
Your contact information:
<div ng:repeat="contact in person.contacts">{{contact.type}}:
<span ng-bind-html="contact.url|linky"></span>
</div>
</div>
</file>
<file name="scenario.js">
it('should navigate to URL', function() {
element('a:contains(Welcome)').click();
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
element('a:contains(Settings)').click();
input('form.name').enter('yourname');
element(':button:contains(Save)').click();
element('a:contains(Welcome)').click();
expect(element('ng\\:view').text()).toMatch(/Hello yourname/);
element('a:contains(Welcome)').click();
expect(element('[ng-view]').text()).toMatch(/Hello anonymous/);
element('a:contains(Settings)').click();
input('form.name').enter('yourname');
element(':button:contains(Save)').click();
element('a:contains(Welcome)').click();
expect(element('[ng-view]').text()).toMatch(/Hello yourname/);
});
</doc:scenario>
</doc:example>
</file>
</example>
@@ -106,7 +145,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
+11 -11
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.
@@ -38,8 +38,8 @@ 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
When you are ready to define your own {@link guide/directive
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
+5 -4
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 guide/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.
@@ -20,4 +21,4 @@ All compilation takes place in the web browser; no server is involved.
## Related API
* {@link api/angular.module.ng.$compile Angular Compiler API}
* {@link api/angular.module.ng.$compileProvider.directive Directives API}
* {@link guide/directive Directives API}
@@ -3,7 +3,7 @@
@description
The {@link api/angular.module.ng.$compile compiler} is responsible for applying
{@link api/angular.module.ng.$compileProvider.directive directives} to the HTML. The directives
{@link guide/directive directives} to the HTML. The directives
extend the behavior of HTML elements and can effect the DOM structure, presentation, and behavior.
This allows Angular to teach the browser new tricks.
@@ -21,7 +21,7 @@ Since directives allow attachment of behavior to the HTML, the angular philosoph
HTML as Domain Specific Language (DSL) when building an application. For example it may be useful
to declare `TabPanel` directive, or `KeyboardShortcut` directive when for an application.
For details on how directives are created see {@link api/angular.module.ng.$compileProvider.directive
For details on how directives are created see {@link guide/directive
directives}
## Related Topics
@@ -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
+24 -28
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 />
@@ -31,7 +31,7 @@ In addition it provides {@link api/angular.module.ng.$compileProvider.directive.
<pre>master = {{master | json}}</pre>
</div>
<script type="text/javascript">
<script>
function Controller($scope) {
$scope.master= {};
@@ -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 />
@@ -95,7 +91,7 @@ This ensures that the user is not distracted with an error until after interacti
}
</style>
<script type="text/javascript">
<script>
function Controller($scope) {
$scope.master= {};
@@ -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,16 +143,16 @@ 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>
<script type="text/javascript">
<script>
function Controller($scope) {
$scope.master= {};
@@ -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.
@@ -231,7 +227,7 @@ In the following example we create two directives.
</form>
</div>
<script type="text/javascript">
<script>
var app = angular.module('form-example1', []);
var INTEGER_REGEXP = /^\-?\d*$/;
@@ -276,22 +272,22 @@ 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.
See {@link guide/directive $compileProvider.directive} for more info.
The following example shows how to add two-way data-binding to contentEditable elements.
<doc:example module="form-example2">
<doc:source>
<script type="text/javascript">
<script>
angular.module('form-example2', []).directive('contenteditable', function() {
return {
require: 'ngModel',
+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
@@ -66,7 +57,7 @@ manipulation—the presentation logic of an application—is well known for bein
Putting any presentation logic into controllers significantly affects testability of the business
logic. Angular offers {@link dev_guide.templates.databinding} for automatic DOM manipulation. If
you have to perform your own manual DOM manipulation, encapsulate the presentation logic in
{@link api/angular.module.ng.$compileProvider.directive directives}.
{@link guide/directive directives}.
- Input formatting — Use {@link dev_guide.forms angular form controls} instead.
- Output filtering — Use {@link dev_guide.templates.filters angular filters} instead.
- Run stateless or stateful code shared across controllers — Use {@link dev_guide.services angular
@@ -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.
@@ -46,8 +46,8 @@ reside on a child scope, if a property read does not find the property on a scop
recursively check the parent scope, grandparent scope, etc. all the way to the root scope before
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
{@link guide/directive directives} associated with elements
(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
@@ -117,14 +117,14 @@ scopes come into play throughout and get a sense of their interactions.
1. At application compile time, a root scope is created and is attached to the root `<HTML>` DOM
element.
2. During the compilation phase, the {@link dev_guide.compiler compiler} matches {@link
api/angular.module.ng.$compileProvider.directive directives} against the DOM template. The directives
guide/directive directives} against the DOM template. The directives
usually fall into one of two categories:
- Observing {@link api/angular.module.ng.$compileProvider.directive directives}, such as double-curly
- Observing {@link guide/directive directives}, such as double-curly
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
@@ -133,9 +133,9 @@ 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
In most cases, {@link guide/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.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,34 +375,34 @@ 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>
<script type="text/javascript">
<script>
function FakeBrowser(initUrl, baseHref) {
this.onUrlChange = function(fn) {
this.urlChange = fn;
@@ -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() {
@@ -30,7 +30,7 @@ myController.$inject = ['$location', '$log'];
<doc:example module="MyServiceModule">
<doc:source>
<script type="text/javascript">
<script>
angular.
module('MyServiceModule', []).
factory('notify', ['$window', function(win) {
@@ -74,7 +74,7 @@ Let's rewrite the above example to show the use of this implicit dependency inje
<doc:example module="MyServiceModuleDI">
<doc:source>
<script type="text/javascript">
<script>
angular.
module('MyServiceModuleDI', []).
factory('notify', function($window) {
@@ -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`.
@@ -18,7 +18,7 @@ text upper-case and assigns color.
<doc:example module="MyReverseModule">
<doc:source>
<script type="text/javascript">
<script>
angular.module('MyReverseModule', []).
filter('reverse', function() {
return function(input, uppercase) {
+4 -4
View File
@@ -10,7 +10,7 @@ the dynamic view DOM.
These are the types of angular elements and element attributes you can use in a template:
* {@link api/angular.module.ng.$compileProvider.directive Directive} — An attribute or element that
* {@link guide/directive Directive} — An attribute or element that
augments an existing DOM element or represents a reusable DOM component - a widget.
* {@link api/angular.module.ng.$interpolate Markup} — The double
curly brace notation `{{ }}` to bind expressions to elements is built-in angular markup.
@@ -21,12 +21,12 @@ Note: In addition to declaring the elements above in templates, you can also ac
in JavaScript code.
The following code snippet shows a simple angular template made up of standard HTML tags along with
angular {@link api/angular.module.ng.$compileProvider.directive directives} and curly-brace bindings
angular {@link guide/directive directives} and curly-brace bindings
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>
@@ -1,5 +1,5 @@
@ngdoc overview
@name angular.module.ng.$compileProvider.directive
@name directive
@description
Directives are a way to teach HTML new tricks. During DOM compilation directives are matched
@@ -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/>
@@ -47,7 +47,7 @@ the following example.
</div>
</doc:source>
<doc:scenario>
it('should load template1.html', function() {
it('should show off bindings', function() {
expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text()).toBe('angular');
});
</doc:scenario>
@@ -55,11 +55,11 @@ the following example.
# String interpolation
During the compilation process the {@link angular.module.ng.$compile compiler} matches text and
attributes using the {@link angular.module.ng.$interpolate $interpolate} service to see if they
During the compilation process the {@link api/angular.module.ng.$compile compiler} matches text and
attributes using the {@link api/angular.module.ng.$interpolate $interpolate} service to see if they
contain embedded expressions. These expressions are registered as {@link
angular.module.ng.$rootScope.Scope#$watch watches} and will update as part of normal {@link
angular.module.ng.$rootScope.Scope#$digest digest} cycle. An example of interpolation is shown
api/angular.module.ng.$rootScope.Scope#$watch watches} and will update as part of normal {@link
api/angular.module.ng.$rootScope.Scope#$digest digest} cycle. An example of interpolation is shown
here:
<pre>
@@ -74,21 +74,21 @@ Compilation of HTML happens in three phases:
realize because the templates must be parsable HTML. This is in contrast to most templating
systems that operate on strings, rather then on DOM elements.
2. The compilation of the DOM is performed by the call to {@link angular.module.ng.$compile
2. The compilation of the DOM is performed by the call to {@link api/angular.module.ng.$compile
$compile()} method. The method traverses the DOM and matches the directives. If a match is found
it is added to the list of directives associated with the given DOM element. Once all directives
for a given DOM element have been identified they are sorted by priority and their `compile()`
functions are executed. The directive compile function has a chance to modify the DOM structure
and is responsible for producing a `link()` function explained next. The {@link
angular.module.ng.$compile $compile()} method returns a combined linking function, which is a
api/angular.module.ng.$compile $compile()} method returns a combined linking function, which is a
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
angular.module.ng.$rootScope.Scope scope}. The result of this is a live binding between the
api/angular.module.ng.$rootScope.Scope#$watch watches} with the {@link
api/angular.module.ng.$rootScope.Scope scope}. The result of this is a live binding between the
scope and the DOM. A change in the scope is reflected in the DOM.
<pre>
@@ -125,14 +125,14 @@ The short answer is that compile and link separation is needed any time a change
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
`{{user}}` is an example of {@link api/angular.module.ng.$interpolate interpolation} directive. {@link
api/angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} is another directive. But {@link
api/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
is not enough. It also needs to compile the `li` so that its directives such as
`{{action.descriptions}}` evaluate against the right {@link angular.module.ng.$rootScope.Scope
`{{action.descriptions}}` evaluate against the right {@link api/angular.module.ng.$rootScope.Scope
scope}. A naive method would be to simply insert a copy of the `li` elemnt and then compile it.
But compiling on every `li` element clone would be slow, since the compilation requires that we
traverse the DOM tree and look for directives and execute them. If we put the compilation inside a
@@ -140,17 +140,17 @@ repeater which needs to unroll 100 items we would quickly run into performance p
The solution is to break the compilation process into two phases the compile phase where all of
the directives are identified and sorted by priority, and a linking phase where any work which
links a specific instance of the {@link angular.module.ng.$rootScope.Scope scope} and the specific
links a specific instance of the {@link api/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 api/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`
api/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 api/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
new {@link api/angular.module.ng.$rootScope.Scope scope} for the cloned `li` element and calls the
link function on the cloned `li`.
Summary:
@@ -288,14 +288,14 @@ further simplification:
## Factory method
The factory method is responsible for creating the directive. It is invoked only once, when the
{@link angular.module.ng.$compile compiler} matches the directive for the first time. You can
{@link api/angular.module.ng.$compile compiler} matches the directive for the first time. You can
perform any initialization work here. The method is invoked using the {@link
http://localhost:8000/build/docs/api/angular.module.AUTO.$injector#invoke $injector.invoke} which
makes it injectable following all of the rules of injection annotation.
## Directive Definition Object
The directive definition object provides instructions to the {@link angular.module.ng.$compile
The directive definition object provides instructions to the {@link api/angular.module.ng.$compile
compiler}. The attributes are:
* `name` - Name of the current scope. Optional defaults to the name at registration.
@@ -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
api/angular.module.ng.$compileProvider.directive.ngRepeat ngRepeat} or load the contents
asynchronously such as {@link api/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
@@ -478,8 +478,8 @@ Link function is responsible for registering DOM listeners as well as updating t
executed after the template has been cloned. This is where most of the directive logic will be
put.
* `scope` - {@link angular.module.ng.$rootScope.Scope Scope} - The scope to be used by the
directive for registering {@link angular.module.ng.$rootScope.Scope#$watch watches}.
* `scope` - {@link api/angular.module.ng.$rootScope.Scope Scope} - The scope to be used by the
directive for registering {@link api/angular.module.ng.$rootScope.Scope#$watch watches}.
* `iElement` - instance element - The element where the directive is to be used. It is safe to
manipulate the children of the element only in `postLink` function since the children have
@@ -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>
+1 -1
View File
@@ -33,7 +33,7 @@ of the following documents before returning here to the Developer Guide:
## {@link dev_guide.compiler Angular HTML Compiler}
* {@link api/angular.module.ng.$compileProvider.directive Understanding Angular Directives}
* {@link guide/directive Understanding Angular Directives}
## {@link dev_guide.templates Angular Templates}
+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!');
});
});
});
+3
View File
@@ -0,0 +1,3 @@
@ngdoc overview
@name Developer Guide: Type
@description
+5 -5
View File
@@ -65,19 +65,19 @@ 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:
* The text input {@link api/angular.module.ng.$compileProvider.directive directive}
* The text input {@link guide/directive directive}
is bound to a model variable called `yourname`.
* The double curly braces notation binds the `yourname` model to the greeting text.
+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 class="diagram" 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.
<div class="tabbable" show="true">
<div class="tab-pane well" 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>java -version</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>git clone git://github.com/angular/angular-phonecat.git</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>cd angular-phonecat</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>
</div>
<div class="tab-pane well" 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>java -version</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>git clone git://github.com/angular/angular-phonecat.git</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>cd angular-phonecat</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>
</div>
<div class="tab-pane well" 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>java -version</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>cd [tutorial-dir]/sandbox</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>
</div>
<div class="tab-pane well" 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>java -version</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>cd [tutorial-dir]/sandbox</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>
</div>
</divs>
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="btn btn-primary">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.
<div class="tabbable" show="true" ng-model="$cookies.platformPreference">
<div class="tab-pane well" id="git-mac" title="Git on Mac/Linux" value="gitUnix">
<ol>
<li><p>In angular-phonecat directory, run this command:</p>
<pre>git checkout -f step-0</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>
</div>
<div class="tab-pane well" id="git-win" title="Git on Windows" value="gitWin">
<ol>
<li><p>Open msysGit bash and run this command (in angular-phonecat directory):</p>
<pre>git checkout -f step-0</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>
</div>
<div class="tab-pane well" id="ss-mac" title="Snapshots on Mac/Linux" value="snapshotUnix">
<ol>
<li><p>In the angular-phonecat directory, run this command:</p>
<pre>./goto_step.sh 0</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>
</div>
<div class="tab-pane well" id="ss-win" title="Snapshots on Windows" value="snapshotWin">
<ol>
<li><p>Open windows command line and run this command (in the angular-phonecat directory):</p>
<pre>goto_step.bat 0</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>
</div>
</div>
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 class="diagram" 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>
+55
View File
@@ -0,0 +1,55 @@
@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.
<div doc-tutorial-reset="1"></div>
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.
<div doc-tutorial-reset="2"></div>
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 class="diagram" 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>
+194
View File
@@ -0,0 +1,194 @@
@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.
<div doc-tutorial-reset="3"></div>
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 class="diagram" 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>
+191
View File
@@ -0,0 +1,191 @@
@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.
<div doc-tutorial-reset="4"></div>
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 class="diagram" 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.
<div doc-tutorial-reset="5"></div>
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 class="diagram" 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.
<div doc-tutorial-reset="6"></div>
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 class="diagram" 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.
<div doc-tutorial-reset="7"></div>
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 class="diagram" 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.
<div doc-tutorial-reset="8"></div>
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 class="diagram" 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.
<div doc-tutorial-reset="9"></div>
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.
<div doc-tutorial-reset="10"></div>
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 class="diagram" 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.
<div doc-tutorial-reset="11"></div>
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}.
-2
View File
@@ -1,2 +0,0 @@
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />
-3
View File
@@ -1,3 +0,0 @@
controller: {{name}}<br />
Book Id: {{prams.bookId}}<br />
Chapter Id: {{params.chapterId}}
-1
View File
@@ -1 +0,0 @@
Hello, $http!
-1
View File
@@ -1 +0,0 @@
Content of template1.html
-1
View File
@@ -1 +0,0 @@
Content of template2.html
-18
View File
@@ -1,18 +0,0 @@
<label>Name:</label>
<input type="text" ng:model="form.name" required>
<div ng:repeat="contact in form.contacts">
<select ng:model="contact.type">
<option>url</option>
<option>email</option>
<option>phone</option>
</select>
<input type="text" ng:model="contact.url">
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<div>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
</div>
<button ng:click="cancel()">Cancel</button>
<button ng:click="save()">Save</button>
-5
View File
@@ -1,5 +0,0 @@
Hello {{person.name}},
<div>
Your contact information:
<div ng:repeat="contact in person.contacts">{{contact.type}}: {{contact.url|linky}}</div>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

+17 -88
View File
@@ -35,19 +35,10 @@ 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');
});
it('should have depth information', function() {
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng-c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].depth).toEqual(2);
expect(ngdoc.metadata([d2])[0].depth).toEqual(2);
expect(ngdoc.metadata([d3])[0].depth).toEqual(1);
});
});
describe('parse', function() {
@@ -89,38 +80,6 @@ describe('ngdoc', function() {
expect(doc.name).toEqual('friendly name');
});
it('should escape <doc:source> element', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:source>\n<>\n</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source">\n&lt;&gt;\n</pre></doc:example><p>after</p>');
});
it('should preserve the source attribute', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:source source="false">lala</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source" source="false">lala</pre></doc:example><p>after</p>');
});
it('should preserve the jsfiddle attribute', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:source jsfiddle="foo">lala</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source" jsfiddle="foo">lala</pre></doc:example><p>after</p>');
});
it('should escape <doc:scenario> element', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-scenario">\n&lt;&gt;\n</pre></doc:example><p>after</p>');
});
it('should store all links', function() {
var doc = new Doc('@name a\n@description {@link api/angular.link}');
doc.parse();
@@ -173,51 +132,33 @@ describe('ngdoc', function() {
});
describe('markdown', function() {
it('should replace angular in markdown', function() {
expect(new Doc().markdown('<angular/>')).
toEqual('<p><tt>&lt;angular/&gt;</tt></p>');
});
it('should not replace anything in <pre>, but escape the html escape the content', function() {
expect(new Doc().markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
toEqual(
'<p>bah x</p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
'<p>bah x\n' +
'<pre class="prettyprint linenums">\n' +
'&lt;b&gt;angular&lt;/b&gt;.k\n' +
'</pre></div>' +
'<p>asdf x</p>');
});
it('should ignore doc widgets', function() {
expect(new Doc().markdown('text<doc:example>do not touch</doc:example>')).
toEqual('<p>text</p><doc:example>do not touch</doc:example>');
expect(new Doc().markdown('text<doc:tutorial-instructions>do not touch</doc:tutorial-instructions>')).
toEqual('<p>text</p><doc:tutorial-instructions>do not touch</doc:tutorial-instructions>');
});
it('should ignore doc widgets with params', function() {
expect(new Doc().markdown('text<doc:tutorial-instructions id="10" show="true">do not touch</doc:tutorial-instructions>')).
toEqual('<p>text</p><doc:tutorial-instructions id="10" show="true">do not touch</doc:tutorial-instructions>');
'</pre>\n' +
' asdf x</p>');
});
it('should replace text between two <pre></pre> tags', function() {
expect(new Doc().markdown('<pre>x</pre># One<pre>b</pre>')).
toMatch('</div><h1>One</h1><div');
expect(new Doc().markdown('<pre>x</pre>\n# One\n<pre>b</pre>')).
toMatch('</pre>\n\n<h1>One</h1>\n\n<pre');
});
it('should ignore nested doc widgets', function() {
expect(new Doc().markdown(
'before<doc:tutorial-instructions>\n' +
'<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">' +
'before<div class="tabbable">\n' +
'<div class="tab-pane well" id="git-mac" ng:model="Git on Mac/Linux">' +
'\ngit bla bla\n</doc:tutorial-instruction>\n' +
'</doc:tutorial-instructions>')).toEqual(
'<p>before</p><doc:tutorial-instructions>\n' +
'<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">\n' +
'<p>before<div class="tabbable">\n' +
'<div class="tab-pane well" id="git-mac" ng:model="Git on Mac/Linux">\n' +
'git bla bla\n' +
'</doc:tutorial-instruction>\n' +
'</doc:tutorial-instructions>');
'</doc:tutorial-instructions></p>');
});
it('should unindent text before processing based on the second line', function() {
@@ -437,18 +378,18 @@ describe('ngdoc', function() {
var doc = new Doc("@name a\n@description <pre><b>abc</b></pre>");
doc.parse();
expect(doc.description).
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">&lt;b&gt;abc&lt;/b&gt;</pre></div>');
toBe('<pre class="prettyprint linenums">&lt;b&gt;abc&lt;/b&gt;</pre>');
});
it('should support multiple pre blocks', function() {
var doc = new Doc("@name a\n@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
doc.parse();
expect(doc.description).
toBe('<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js;">abc</pre></div>' +
toBe('<p>foo \n' +
'<pre class="prettyprint linenums">abc</pre>\n\n' +
'<h1>bah</h1>\n\n' +
'<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js;">cba</pre></div>');
'<p>foo \n' +
'<pre class="prettyprint linenums">cba</pre>');
});
@@ -493,18 +434,6 @@ describe('ngdoc', function() {
doc.parse();
expect(doc.example).toEqual('<p>text {{ abc }}</p>');
});
it('should support doc:example', function() {
var doc = new Doc('@name a\n@ngdoc overview\n@example \n' +
'<doc:example>\n' +
' <doc:source><escapeme></doc:source>\n' +
' <doc:scenario><scenario></doc:scenario>\n' +
'</doc:example>').parse();
var html = doc.html();
expect(html).toContain('<pre class="doc-source">&lt;escapeme&gt;</pre>');
expect(html).toContain('<pre class="doc-scenario">&lt;scenario&gt;</pre>');
expect(doc.scenarios).toEqual(['<scenario>']);
});
});
describe('@deprecated', function() {
+1 -3
View File
@@ -70,9 +70,7 @@ DOM.prototype = {
},
code: function(text) {
this.tag('div', {'ng:non-bindable':''}, function() {
this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
});
this.tag('pre', {'class':"prettyprint linenums"}, text);
},
div: function(attr, text) {
+126
View File
@@ -0,0 +1,126 @@
var seqCount = 0;
var usedIds = {};
var makeUnique = {
'index.html': true,
'style.css': true,
'script.js': true,
'unit.js': true,
'spec.js': true,
'scenario.js': true
}
function ids(list) {
return list.map(function(item) { return item.id; }).join(' ');
};
exports.Example = function(scenarios) {
this.module = '';
this.deps = ['angular.js'];
this.html = [];
this.css = [];
this.js = [];
this.unit = [];
this.scenario = [];
this.scenarios = scenarios;
}
exports.Example.prototype.setModule = function(module) {
if (module) {
this.module = module;
}
};
exports.Example.prototype.addDeps = function(deps) {
deps && deps.split(/[\s\,]/).forEach(function(dep) {
if (dep) {
this.deps.push(dep);
}
}, this);
};
exports.Example.prototype.addSource = function(name, content) {
var ext = name == 'scenario.js' ? 'scenario' : name.split('.')[1],
id = name;
if (makeUnique[name] && usedIds[id]) {
id = name + '-' + (seqCount++);
}
usedIds[id] = true;
this[ext].push({name: name, content: content, id: id});
if (name.match(/\.js$/) && name !== 'spec.js' && name !== 'unit.js' && name != 'scenario.js') {
this.deps.push(name);
}
if (ext == 'scenario') {
this.scenarios.push(content);
}
};
exports.Example.prototype.toHtml = function() {
return '<h1>Demo Source Code</h1>\n' +
this.toHtmlEdit() +
this.toHtmlTabs() +
'<h1>Demo Source Code</h1>\n' +
this.toHtmlEmbed();
};
exports.Example.prototype.toHtmlEdit = function() {
var out = [];
out.push('<div source-edit="' + this.module + '"');
out.push(' source-edit-deps="' + this.deps.join(' ') + '"');
out.push(' source-edit-html="' + ids(this.html) + '"');
out.push(' source-edit-css="' + ids(this.css) + '"');
out.push(' source-edit-js="' + ids(this.js) + '"');
out.push(' source-edit-unit="' + ids(this.unit) + '"');
out.push(' source-edit-scenario="' + ids(this.scenario) + '"');
out.push('></div>\n');
return out.join('');
};
exports.Example.prototype.toHtmlTabs = function() {
var out = [],
self = this;
out.push('<div class="tabbable">');
htmlTabs(this.html);
htmlTabs(this.css);
htmlTabs(this.js);
htmlTabs(this.unit);
htmlTabs(this.scenario);
out.push('</div>');
return out.join('');
function htmlTabs(sources) {
sources.forEach(function(source) {
var wrap = '',
isCss = source.name.match(/\.css$/),
name = source.name;
if (name === 'index.html') {
wrap = ' ng-html-wrap="' + self.module + ' ' + self.deps.join(' ') + '"';
}
if (name == 'scenario.js') name = 'End to end test';
out.push(
'<div class="tab-pane" title="' + name + '">\n' +
'<pre class="prettyprint linenums" ng-set-text="' + source.id + '"' + wrap + '></pre>\n' +
(isCss
? ('<style type="text/css" id="' + source.id + '">' + source.content + '</style>\n')
: ('<script type="text/ng-template" id="' + source.id + '">' + source.content + '</script>\n') ) +
'</div>\n');
});
}
};
exports.Example.prototype.toHtmlEmbed = function() {
var out = [];
out.push('<div class="well doc-example-live"');
out.push(' ng-embed-app="' + this.module + '"');
out.push(' ng-set-html="' + this.html[0].id + '"');
out.push(' ng-eval-javascript="' + ids(this.js) + '">');
out.push('</div>');
return out.join('');
};
+13 -22
View File
@@ -41,35 +41,35 @@ function writeTheRest(writesFuture) {
var metadata = ngdoc.metadata(docs);
writesFuture.push(writer.copyDir('img'));
writesFuture.push(writer.copyDir('examples'));
var manifest = 'manifest="/build/docs/appcache.manifest"';
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index.html',
writesFuture.push(writer.copy('docs/src/templates/index.html', 'index.html',
writer.replace, {'doc:manifest': ''})); //manifest //TODO(i): enable
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-nocache.html',
writesFuture.push(writer.copy('docs/src/templates/index.html', 'index-nocache.html',
writer.replace, {'doc:manifest': ''}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html',
writesFuture.push(writer.copy('docs/src/templates/index.html', 'index-jq.html',
writer.replace, {'doc:manifest': manifest}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-nocache.html',
writesFuture.push(writer.copy('docs/src/templates/index.html', 'index-jq-nocache.html',
writer.replace, {'doc:manifest': ''}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-debug.html',
writesFuture.push(writer.copy('docs/src/templates/index.html', 'index-debug.html',
writer.replace, {'doc:manifest': ''}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-debug.html',
writesFuture.push(writer.copy('docs/src/templates/index.html', 'index-jq-debug.html',
writer.replace, {'doc:manifest': ''}));
writesFuture.push(writer.copyTpl('offline.html'));
writesFuture.push(writer.copyTpl('docs-scenario.html'));
writesFuture.push(writer.copyTpl('jquery.min.js'));
writesFuture.push(writer.copyTpl('js/jquery.min.js'));
writesFuture.push(writer.copyTpl('js/jquery.js'));
writesFuture.push(writer.output('docs-keywords.js',
writesFuture.push(writer.output('js/docs-keywords.js',
['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';']));
writesFuture.push(writer.output('sitemap.xml', new SiteMap(docs).render()));
writesFuture.push(writer.output('docs-scenario.js', ngdoc.scenarios(docs)));
@@ -77,19 +77,10 @@ function writeTheRest(writesFuture) {
writesFuture.push(writer.output('appcache.manifest',appCache()));
writesFuture.push(writer.copyTpl('.htaccess'));
writesFuture.push(writer.merge(['docs.js',
'doc_widgets.js'],
'docs-combined.js'));
writesFuture.push(writer.merge(['docs.css',
'doc_widgets.css'],
'docs-combined.css'));
writesFuture.push(writer.merge(['syntaxhighlighter/shCore.js',
'syntaxhighlighter/shBrushJScript.js',
'syntaxhighlighter/shBrushXml.js'],
'syntaxhighlighter/syntaxhighlighter-combined.js'));
writesFuture.push(writer.merge(['syntaxhighlighter/shCore.css',
'syntaxhighlighter/shThemeDefault.css'],
'syntaxhighlighter/syntaxhighlighter-combined.css'));
writesFuture.push(writer.copy('docs/src/templates/js/docs.js', 'js/docs.js'));
writesFuture.push(writer.copy('docs/src/templates/css/bootstrap.min.css', 'css/bootstrap.min.css'));
writesFuture.push(writer.copy('docs/src/templates/css/docs.css', 'css/docs.css'));
}
+193 -94
View File
@@ -5,7 +5,9 @@
var Showdown = require('../../lib/showdown').Showdown;
var DOM = require('./dom.js').DOM;
var htmlEscape = require('./dom.js').htmlEscape;
var Example = require('./example.js').Example;
var NEW_LINE = /\n\r?/;
var globalID = 0;
exports.trim = trim;
exports.metadata = metadata;
@@ -98,72 +100,85 @@ Doc.prototype = {
if (!text) return text;
var self = this,
IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/,
IS_ANGULAR = /^(api\/)?angular\./,
IS_HASH = /^#/,
parts = trim(text).split(/(<pre>[\s\S]*?<\/pre>|<doc:(\S*).*?>[\s\S]*?<\/doc:\2>)/);
IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/,
IS_ANGULAR = /^(api\/)?angular\./,
IS_HASH = /^#/,
parts = trim(text).split(/(<pre>[\s\S]*?<\/pre>|<doc:example(\S*).*?>[\s\S]*?<\/doc:example>|<example[^>]*>[\s\S]*?<\/example>)/),
seq = 0,
placeholderMap = {};
function placeholder(text) {
var id = 'REPLACEME' + (seq++);
placeholderMap[id] = text;
return id;
}
parts.forEach(function(text, i) {
parts[i] = (text || '').
replace(/<example(?:\s+module="([^"]*)")?(?:\s+deps="([^"]*)")?>([\s\S]*?)<\/example>/gmi, function(_, module, deps, content) {
var example = new Example(self.scenarios);
function isDocWidget(name) {
if ((i + 1) % 3 != 2) return false;
if (name) return parts[i+1] == name;
return !!parts[i+1];
}
example.setModule(module);
example.addDeps(deps);
content.replace(/<file\s+name="([^"]*)"\s*>([\s\S]*?)<\/file>/gmi, function(_, name, content) {
example.addSource(name, content);
});
return placeholder(example.toHtml());
}).
replace(/^<doc:example(\s+[^>]*)?>([\s\S]*)<\/doc:example>/mi, function(_, attrs, content) {
var html, script, scenario,
example = new Example(self.scenarios);
// ignore each third item which is doc widget tag
if (!((i + 1) % 3)) {
parts[i] = '';
return;
}
example.setModule((attrs||'module=""').match(/^\s*module=["'](.*)["']\s*$/)[1]);
content.
replace(/<doc:source(\s+[^>]*)?>([\s\S]*)<\/doc:source>/mi, function(_, attrs, content) {
example.addSource('index.html', content.
replace(/<script>([\s\S]*)<\/script>/mi, function(_, script) {
example.addSource('script.js', script);
return '';
}).
replace(/<style>([\s\S]*)<\/style>/mi, function(_, style) {
example.addSource('style.css', style);
return '';
})
);
}).
replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi, function(_, before, content){
example.addSource('scenario.js', content);
});
if (text.match(/^<pre>/)) {
text = text.replace(/^<pre>([\s\S]*)<\/pre>/mi, function(_, content){
var clazz = 'brush: js;';
if (content.match(/\<\w/)) {
// we are HTML
clazz += ' html-script: true;';
}
return '<div ng:non-bindable><pre class="' + clazz +'">' +
content.replace(/</g, '&lt;').replace(/>/g, '&gt;') +
'</pre></div>';
return placeholder(example.toHtml());
}).
replace(/^<pre>([\s\S]*?)<\/pre>/mi, function(_, content){
return placeholder(
'<pre class="prettyprint linenums">' +
content.replace(/</g, '&lt;').replace(/>/g, '&gt;') +
'</pre>');
}).
replace(/<div([^>]*)><\/div>/, '<div$1>\n<\/div>').
replace(/{@link\s+([^\s}]+)\s*([^}]*?)\s*}/g, function(_all, url, title){
var isFullUrl = url.match(IS_URL),
isAngular = url.match(IS_ANGULAR),
isHash = url.match(IS_HASH),
absUrl = isHash
? url
: (isFullUrl ? url : self.convertUrlToAbsolute(url));
if (!isFullUrl) self.links.push(absUrl);
return '<a href="' + absUrl + '">' +
(isAngular ? '<code>' : '') +
(title || url).replace(/^#/g, '').replace(/\n/g, ' ') +
(isAngular ? '</code>' : '') +
'</a>';
});
} else if (isDocWidget('example')) {
text = text.replace(/<doc:source(\s+[^>]*)?>([\s\S]*)<\/doc:source>/mi,
function(_, attrs, content){
return '<pre class="doc-source"' + (attrs || '') +'>' +
htmlEscape(content) +
'</pre>';
});
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
function(_, before, content){
self.scenarios.push(content);
return '<pre class="doc-scenario">' + htmlEscape(content) + '</pre>';
});
} else if (!isDocWidget()) {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
text = text.replace(/{@link\s+([^\s}]+)\s*([^}]*?)\s*}/g,
function(_all, url, title){
var isFullUrl = url.match(IS_URL),
isAngular = url.match(IS_ANGULAR),
isHash = url.match(IS_HASH),
absUrl = isHash
? url
: (isFullUrl ? url : self.convertUrlToAbsolute(url));
if (!isFullUrl) self.links.push(absUrl);
return '<a href="' + absUrl + '">' +
(isAngular ? '<code>' : '') +
(title || url).replace(/^#/g, '').replace(/\n/g, ' ') +
(isAngular ? '</code>' : '') +
'</a>';
});
text = new Showdown.converter().makeHtml(text);
}
parts[i] = text;
});
return parts.join('');
text = parts.join('');
text = new Showdown.converter().makeHtml(text);
text = text.replace(/(?:<p>)?(REPLACEME\d+)(?:<\/p>)?/g, function(_, id) {
return placeholderMap[id];
});
return text;
},
parse: function() {
@@ -200,7 +215,7 @@ Doc.prototype = {
var text = trim(atText.join('\n')), match;
if (atName == 'param') {
match = text.match(/^\{([^}=]+)(=)?\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 12 2 34 4 5 5 6 6 3 7 7
// 1 12 2 34 4 5 5 6 6 3 7 7
if (!match) {
throw new Error("Not a valid 'param' format: " + text);
}
@@ -233,11 +248,11 @@ Doc.prototype = {
throw new Error("Not a valid 'property' format: " + text);
}
var property = new Doc({
type: match[1],
name: match[2],
shortName: match[2],
description: self.markdown(text.replace(match[0], match[4]))
});
type: match[1],
name: match[2],
shortName: match[2],
description: self.markdown(text.replace(match[0], match[4]))
});
self.properties.push(property);
} else if(atName == 'eventType') {
match = text.match(/^([^\s]*)\s+on\s+([\S\s]*)/);
@@ -252,9 +267,9 @@ Doc.prototype = {
html: function() {
var dom = new DOM(),
self = this;
self = this;
dom.h(this.name, function() {
dom.h(title(this.name), function() {
notice('deprecated', 'Deprecated API', self.deprecated);
if (self.ngdoc != 'overview') {
@@ -336,9 +351,11 @@ Doc.prototype = {
html_usage_function: function(dom){
var self = this;
var name = self.name.match(/^angular(\.mock)?\.(\w+)$/) ? self.name : self.name.split(/\./).pop()
dom.h('Usage', function() {
dom.code(function() {
dom.text(self.name.split(/\./).pop());
dom.text(name);
dom.text('(');
self.parameters(dom, ', ');
dom.text(');');
@@ -372,10 +389,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 +401,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 +412,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 +484,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 ? ']' : '');
});
@@ -549,12 +566,12 @@ Doc.prototype = {
dom.div({class:'member property'}, function(){
dom.h('Properties', self.properties, function(property){
dom.h(property.shortName, function() {
dom.html(property.description);
if (!property.html_usage_returns) {
console.log(property);
}
property.html_usage_returns(dom);
dom.h('Example', property.example, dom.html);
dom.html(property.description);
if (!property.html_usage_returns) {
console.log(property);
}
property.html_usage_returns(dom);
dom.h('Example', property.example, dom.html);
});
});
});
@@ -605,6 +622,74 @@ Doc.prototype = {
//////////////////////////////////////////////////////////
var GLOBALS = /^angular\.([^\.]*)$/,
MODULE = /^angular\.module\.([^\.]*)$/,
MODULE_MOCK = /^angular\.mock\.([^\.]*)$/,
MODULE_DIRECTIVE = /^angular\.module\.([^\.]*)(?:\.\$compileProvider)?\.directive\.([^\.]*)$/,
MODULE_DIRECTIVE_INPUT = /^angular\.module\.([^\.]*)\.\$compileProvider\.directive\.input\.([^\.]*)$/,
MODULE_FILTER = /^angular\.module\.([^\.]*)\.\$?filter\.([^\.]*)$/,
MODULE_SERVICE = /^angular\.module\.([^\.]*)\.([^\.]*?)(Provider)?$/,
MODULE_TYPE = /^angular\.module\.([^\.]*)\..*\.([A-Z][^\.]*)$/;
function title(text) {
if (!text) return text;
var match,
module,
type,
name;
if (text == 'angular.Module') {
module = 'ng';
name = 'Module';
type = 'Type';
} else if (match = text.match(GLOBALS)) {
module = 'ng';
name = 'angular.' + match[1];
type = 'API';
} else if (match = text.match(MODULE)) {
module = match[1];
} else if (match = text.match(MODULE_MOCK)) {
module = 'ng';
name = 'angular.mock.' + match[1];
type = 'API';
} else if (match = text.match(MODULE_DIRECTIVE)) {
module = match[1];
name = match[2];
type = 'directive';
} else if (match = text.match(MODULE_DIRECTIVE_INPUT)) {
module = match[1];
name = 'input [' + match[2] + ']';
type = 'directive';
} else if (match = text.match(MODULE_FILTER)) {
module = match[1];
name = match[2];
type = 'filter';
} else if (match = text.match(MODULE_SERVICE)) {
module = match[1];
name = match[2] + (match[3] || '');
type = 'service';
} else if (match = text.match(MODULE_TYPE)) {
module = match[1];
name = match[2];
type = 'type';
} else {
return text;
}
return function() {
this.tag('code', name);
this.tag('span', { class: 'hint'}, function() {
if (type) {
this.text('(');
this.text(type);
this.text(' in module ');
this.tag('code', module);
this.text(')');
}
});
};
}
function scenarios(docs){
var specs = [];
@@ -629,7 +714,7 @@ function scenarios(docs){
specs.push(' });');
specs.push(' ');
doc.scenarios.forEach(function(scenario){
specs.push(indent(trim(scenario), 4));
specs.push(indentCode(trim(scenario), 4));
specs.push('');
});
specs.push('});');
@@ -647,13 +732,16 @@ function metadata(docs){
for ( var i = 1; i < path.length; i++) {
path.splice(i, 1);
}
var depth = path.length - 1;
var shortName = path.pop();
if (path.pop() == 'input') {
shortName = 'input [' + shortName + ']';
}
words.push({
section: doc.section,
id: doc.id,
name: doc.name,
depth: depth,
name: title(doc.name),
shortName: shortName,
type: doc.ngdoc,
keywords:doc.keywords()
@@ -669,16 +757,19 @@ var KEYWORD_PRIORITY = {
'.angular': 7,
'.angular.Module': 7,
'.angular.module': 8,
'.angular.module.ng.$filter': 7,
'.angular.module.ng.$rootScope.Scope': 7,
'.angular.module.ng': 7,
'.angular.mock': 8,
'.angular.directive': 6,
'.angular.module.ngMock': 8,
'.angular.module.ng': 2,
'.angular.module.AUTO': 1,
'.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,
@@ -711,7 +802,7 @@ function trim(text) {
var minIndent = MAX_INDENT;
var indentRegExp;
var ignoreLine = (lines[0][0] != ' ' && lines.length > 1);
// ignore first line if it has no indentation and there is more than one line
// ignore first line if it has no indentation and there is more than one line
lines.forEach(function(line){
if (ignoreLine) {
@@ -743,10 +834,10 @@ function trim(text) {
return lines.join('\n');
}
function indent(text, spaceCount) {
function indentCode(text, spaceCount) {
var lines = text.split('\n'),
indent = '',
fixedLines = [];
indent = '',
fixedLines = [];
while(spaceCount--) indent += ' ';
@@ -795,7 +886,7 @@ function merge(docs){
var parent = byFullId['api/' + parentName];
if (!parent)
throw new Error("No parent named '" + parentName + "' for '" +
doc.name + "' in @" + name + "Of.");
doc.name + "' in @" + name + "Of.");
var listName = (name + 's').replace(/ys$/, 'ies');
var list = parent[listName] = (parent[listName] || []);
@@ -815,3 +906,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();
});
}
-668
View File
@@ -1,668 +0,0 @@
@charset "UTF-8";
body {
background: #000 url("../images/texture_1.png");
}
/*----- Layout Generic Styles -----*/
body,td,th {
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
color: #000;
margin-top: 0px;
}
a:link {
color: #5d6db6;
text-decoration: none;
}
a:visited {
text-decoration: none;
color: #7989D6;
}
a:hover {
text-decoration: underline;
color: #5d6db6;
}
a:active {
text-decoration: none;
}
p {
font-size: 14px;
padding-right: 10px;
padding-left: 15px;
}
.h1 {
font-size: 24px;
color: #000;
text-align: left;
font-weight: bold;
}
.h2 {
font-size: 18px;
text-align: left;
text-indent: 10px;
}
ul {
font-size: 16px;
line-height: 22px;
list-style: url(images/bullet.png) outside;
}
li {
font-size: 16px;
line-height: 22px;
list-style: url(../images/bullet.png) outside;
}
/*----- Global Layout -----*/
.twoCol #container {
width: 1050px;
text-align: left;
margin: 0px auto;
background: #FC0 url(../images/yellow_bkgnd.jpg) no-repeat 0px;
z-index: 1;
}
.home #container {
width: 1050px;
text-align: left;
margin: 0px auto;
background: #FC0 url(../images/yellow_bkgnd.jpg) no-repeat;
top: 0px;
height: 1650px;
}
#homeFooter {
float: right;
height: 30px;
width: 275px;
margin-top: 80px;
font-size: 12px;
}
#twoColFooter {
font-size: 12px;
height: 30px;
width: 275px;
padding-left: 20px;
margin-left: 700px;
position: relative;
bottom: 30px;
margin-top: 100px;
}
#navigationHome {
background: #7989D6;
height: 50px;
width: 1050px;
position: relative;
top: 0px;
z-index: 3;
}
#navigation {
height: 50px;
width: 1050px;
position: fixed;
z-index: 10;
top: 0px;
background: #7989D6;
}
/*----- navigation styles -----*/
#navContainer {
position: absolute;
top: 12px;
width: 1050px;
font-size: 17px;
}
#navContainer ul {
margin:0;
list-style:none;
}
.navContainerStyle li{
display: inline;
list-style-type: none;
padding-right:30px;
font-size: 17px;
color: #FFF;
text-decoration: none;
padding-left: 35px;
}
.navContainerStyle li a:link, .navContainerStyle li a:visited{
display:inline;
font-size: 1em;
font-weight:300;
color:#FFF;
text-decoration:none;
padding: 8px 12px;
}
.navContainerStyle li a:hover{
color:#000
}
.navContainerStyle li a.current, .navContainerStyle li a.current:hover, .navContainerStyle a.current:active {
color: #FFF;
cursor:auto;
background:#000;
-moz-border-radius: 15px;
border-radius: 15px;
-webkit-border-radius:15px;
}
/*------ sidebar styles ------*/
#left {
float: left;
height: 1080px;
width: 445px;
margin-left: 40px;
}
.twoCol #sidebar{
float:left;
width:245px;
padding:0;
margin-top: 120px;
position: relative;
z-index: 3;
}
#sidebarTop {
height:49px;
width: 235px;
background: #7989D6;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius-topright: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius-topright: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-top-right-radius: 15px;
border-top-right-radius: 15px;
}
#sidebarArea {
width: 234px;
top: 0px;
padding-bottom: 1px;
background: #FFF;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius-bottomright: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius-bottomright: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-bottom-right-radius: 15px;
border-bottom-right-radius: 15px;
}
#sideBarContent1 {
width: 200px;
padding-top: 20px;
padding-right: 20px;
position: relative;
padding-left: 10px;
}
#sidebarBottom {
background: url(../images/sidebarBottom.png) no-repeat top;
width: 234px;
margin-bottom: 20px;
height: 28px;
}
/*----- textbox content -----*/
#textbox {
float:right;
width: 729px;
position: relative;
z-index: 5;
margin-top: 120px;
margin-right: 35px; /*margin: 120px 40px 80px 278px;*/
}
#textboxTop {
position: relative;
height: 49px;
top: 0px;
left: 0px;
background: #7989D6;
width: 729px;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius-topright: 15px;
-moz-border-radius-topleft: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius-topright: 15px;
border-radius-topleft: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
border-top-right-radius: 15px;
border-top-left-radius: 15px;
}
#textbox_content {
padding: 20px 20px 5px;
width: 685px;
position: relative;
background: #FFF;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius-bottomright: 15px;
-moz-border-radius-bottomleft: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius-bottomright: 15px;
border-radius-bottomleft: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-radius-bottomright: 15px;
-webkit-border-radius-bottomleft: 15px;
border-right: 5px solid #FFF;
border-bottom: 5px solid #FFF;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
border-bottom-left-radius: 15px;
}
.textboxContentHeader {
font-family: Arial, Helvetica, sans-serif;
font-size: 28px;
color: #FFF;
padding: 9px 8px 8px;
margin-left: 20px;
}
/*----- home page -----*/
#top {
height: 400px;
width: 750px;
clear: both;
float: left;
}
#logo {
height: 72px;
width: 440px;
margin-top: 50px;
margin-left: 45px;
font-family: "Courier New", Courier, monospace;
font-size: 68px;
color: #7989D6;
font-weight: 400;
color:#7690CF;
text-shadow:1.5px 1.5px 1.5px #48577D;
text-shadow:#48577D 1.5px 1.5px 1.5px;
letter-spacing: 1.5px;
}
#twitterWidget {
float: right;
height: 300px;
width: 250px;
top: 0px;
margin-top: 45px;
margin-right: 40px;
}
#tagline {
height: 120px;
width: 600px;
margin-left: 93px;
clear: both;
float: left;
margin-top: 10px;
font-size: 40px;
color: #FFF;
font-weight: bold;
text-shadow:1.5px 1.5px 1.5px #48577D;
text-shadow:#48577D 1.5px 1.5px 1.5px;
}
/*----- buttons -----*/
#buttons {
clear: both;
float: left;
height: 72px;
width: 675px;
margin-left: 40px;
margin-top: 35px;
}
#downloadButton {
clear: both;
float: left;
height: 72px;
width: 230px;
background: #FFF url(../images/download_arrow.png) no-repeat 15px 5px;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 10px;
box-shadow: 4px 4px 6px #48577D;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-radius:10px;
}
#downloadText {
padding-top: 7px;
padding-left: 58px;
width: 170px;
}
#communityButton {
float: right;
height: 72px;
width: 230px;
background: #FFF url(../images/community_icon.png) no-repeat 147px 8px;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 10px;
box-shadow: 4px 4px 6px #48577D;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-radius:10px;
}
#CommunityText {
width: 150px;
padding-top: 7px;
padding-left: 20px;
}
.smallLinks {
font-size: 10px;
line-height: 14px;
color: #000;
}
.smallLinks a:link {
color: #000;
text-decoration: none;
}
.smallLinks a:visited {
text-decoration: none;
color: #000;
}
.smallLinks a:hover {
text-decoration: underline;
color: #7989D6;
}
.smallLinks a:active {
text-decoration: none;
}
.blueText {
font-size: 13px;
color: #7989D6;
}
.ButtonHeadings {
font-size: 22px;
font-weight: bold;
}
/*----- Icon Divs ----- */
#icons {
background: #FFF;
width: 410px;
clear: both;
float: left;
height: 520px;
width: 410px;
padding-top: 20px;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-radius:15px;
}
#iconWrap{
clear: right;
float: left;
height: 500px;
width: 110px;
margin-left: 30px;
padding-top: 16px;
}
#templatesDescription {
font-size: 14px;
line-height: 16px;
height: 95px;
width: 300px;
}
#Descriptions {
float: left;
height: 500px;
width: 230px;
padding-top: 16px;
font-size: 14px;
line-height: 16px;
margin-left: 5px;
}
#UItemplatesDescription {
height: 95px;
width: 230px;
margin-bottom: 25px;
padding-top: 5px;
}
#two-wayDescription {
height: 85px;
width: 230px;
margin-bottom: 10px;
padding-top: 20px;
}
#frameworkDescription {
height: 95px;
width: 230px;
margin-bottom: 25px;
padding-top: 7px;
}
#mvcDescription {
height: 75px;
width: 230px;
padding-top: 18px;
}
#templateIcon {
height: 90px;
width: 95px;
position: relative;
}
#two-wayIcon {
height: 90px;
width: 95px;
position: relative;
}
#frameworkIcon {
height: 90px;
width: 95px;
position: relative;
}
#mvcIcon {
height: 90px;
width: 95px;
position: relative;
}
#templatesLink {
line-height: 12px;
height: 28px;
width: 95px;
margin-bottom: 5px;
padding-top: 3px;
text-align: center;
}
#two-wayLink {
line-height: 12px;
height: 28px;
width: 95px;
margin-bottom: 5px;
padding-top: 3px;
text-align: center;
}
#frameworkLink {
line-height: 12px;
height: 20px;
width: 95px;
margin-bottom: 5px;
padding-top: 5px;
text-align: center;
}
#mvcLink {
line-height: 12px;
height: 28px;
width: 95px;
margin-bottom: 5px;
padding-top: 3px;
text-align: center;
}
/*----- What you need divs ---*/
#whatYouNeed {
clear: both;
float: left;
margin-top: 35px;
margin-bottom: 35px;
background: #FFF;
height: 240px;
width: 410px;
padding-top: 20px;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-radius:15px;
}
#listText {
width: 350px;
padding-left: 20px;
}
/*----- testimonial divs ---*/
#testimonials {
background: #FFF;
height: 175px;
width: 390px;
clear: both;
float: left;
padding-top: 20px;
padding-left: 20px;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-radius:15px;
}
#testimonialsText {
width: 340px;
margin-left: 20px;
padding-top: 15px;
}
#sigName {
width: 340px;
font-size: 12px;
font-style: italic;
text-align: right;
}
/* ------ Live Example Divs ------ */
#right {
width: 525px;
clear: right;
float: left;
position: relative;
}
#live {
width: 525px;
position: relative;
margin-top: 370px;
float: left;
}
#LiveExText {
font-size: 14px;
background: #FFF;
width: 475px;
height: 1045px;
padding-top: 20px;
padding-left: 25px;
padding-right: 25px;
-moz-box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 15px;
box-shadow: 4px 4px 6px #48577D;
border-radius: 15px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-webkit-border-radius:15px;
}
#Example {
font-size: 14px;
width: 480px;
padding-right: 25px;
padding-top: 35px;
padding-left: 0px;
}
/*-----float clearing---*/
.clearFloat {
clear: both;
height:0px;
font-size:1px;
line-height:0px;
}
+689
View File
@@ -0,0 +1,689 @@
article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
audio:not([controls]){display:none;}
html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
a:hover,a:active{outline:0;}
sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
sup{top:-0.5em;}
sub{bottom:-0.25em;}
img{height:auto;border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;}
button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
button,input{*overflow:visible;line-height:normal;}
button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
textarea{overflow:auto;vertical-align:top;}
.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}
.clearfix:after{clear:both;}
.hide-text{overflow:hidden;text-indent:100%;white-space:nowrap;}
.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}
body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;}
a{color:#0088cc;text-decoration:none;}
a:hover{color:#005580;text-decoration:underline;}
.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";}
.row:after{clear:both;}
[class*="span"]{float:left;margin-left:20px;}
.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
.span12{width:940px;}
.span11{width:860px;}
.span10{width:780px;}
.span9{width:700px;}
.span8{width:620px;}
.span7{width:540px;}
.span6{width:460px;}
.span5{width:380px;}
.span4{width:300px;}
.span3{width:220px;}
.span2{width:140px;}
.span1{width:60px;}
.offset12{margin-left:980px;}
.offset11{margin-left:900px;}
.offset10{margin-left:820px;}
.offset9{margin-left:740px;}
.offset8{margin-left:660px;}
.offset7{margin-left:580px;}
.offset6{margin-left:500px;}
.offset5{margin-left:420px;}
.offset4{margin-left:340px;}
.offset3{margin-left:260px;}
.offset2{margin-left:180px;}
.offset1{margin-left:100px;}
.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";}
.row-fluid:after{clear:both;}
.row-fluid>[class*="span"]{float:left;margin-left:2.127659574%;}
.row-fluid>[class*="span"]:first-child{margin-left:0;}
.row-fluid > .span12{width:99.99999998999999%;}
.row-fluid > .span11{width:91.489361693%;}
.row-fluid > .span10{width:82.97872339599999%;}
.row-fluid > .span9{width:74.468085099%;}
.row-fluid > .span8{width:65.95744680199999%;}
.row-fluid > .span7{width:57.446808505%;}
.row-fluid > .span6{width:48.93617020799999%;}
.row-fluid > .span5{width:40.425531911%;}
.row-fluid > .span4{width:31.914893614%;}
.row-fluid > .span3{width:23.404255317%;}
.row-fluid > .span2{width:14.89361702%;}
.row-fluid > .span1{width:6.382978723%;}
.container{margin-left:auto;margin-right:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";}
.container:after{clear:both;}
.container-fluid{padding-left:20px;padding-right:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";}
.container-fluid:after{clear:both;}
p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p small{font-size:11px;color:#999999;}
.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;}
h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;}
h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;}
h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;}
h3{line-height:27px;font-size:18px;}h3 small{font-size:14px;}
h4,h5,h6{line-height:18px;}
h4{font-size:14px;}h4 small{font-size:12px;}
h5{font-size:12px;}
h6{font-size:11px;color:#999999;text-transform:uppercase;}
.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;}
.page-header h1{line-height:1;}
ul,ol{padding:0;margin:0 0 9px 25px;}
ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
ul{list-style:disc;}
ol{list-style:decimal;}
li{line-height:18px;}
ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
dl{margin-bottom:18px;}
dt,dd{line-height:18px;}
dt{font-weight:bold;line-height:17px;}
dd{margin-left:9px;}
.dl-horizontal dt{float:left;clear:left;width:120px;text-align:right;}
.dl-horizontal dd{margin-left:130px;}
hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}
strong{font-weight:bold;}
em{font-style:italic;}
.muted{color:#999999;}
abbr[title]{border-bottom:1px dotted #ddd;cursor:help;}
abbr.initialism{font-size:90%;text-transform:uppercase;}
blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;}
blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
q:before,q:after,blockquote:before,blockquote:after{content:"";}
address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;}
small{font-size:100%;}
cite{font-style:normal;}
code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;white-space:pre;white-space:pre-wrap;word-break:break-all;word-wrap:break-word;}pre.prettyprint{margin-bottom:18px;}
pre code{padding:0;color:inherit;background-color:transparent;border:0;}
.pre-scrollable{max-height:340px;overflow-y:scroll;}
form{margin:0 0 18px;}
fieldset{padding:0;margin:0;border:0;}
legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;}legend small{font-size:13.5px;color:#999999;}
label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;}
input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
label{display:block;margin-bottom:5px;color:#333333;}
input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;border:1px solid #cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.uneditable-textarea{width:auto;height:auto;}
label input,label textarea,label select{display:block;}
input[type="image"],input[type="checkbox"],input[type="radio"]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border:0 \9;}
input[type="image"]{border:0;}
input[type="file"]{width:auto;padding:initial;line-height:initial;border:initial;background-color:#ffffff;background-color:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
input[type="button"],input[type="reset"],input[type="submit"]{width:auto;height:auto;}
select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;}
input[type="file"]{line-height:18px \9;}
select{width:220px;background-color:#ffffff;}
select[multiple],select[size]{height:auto;}
input[type="image"]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
textarea{height:auto;}
input[type="hidden"]{display:none;}
.radio,.checkbox{padding-left:18px;}
.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}
.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
input,textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;}
input:focus,textarea:focus{border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);outline:0;outline:thin dotted \9;}
input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.input-mini{width:60px;}
.input-small{width:90px;}
.input-medium{width:150px;}
.input-large{width:210px;}
.input-xlarge{width:270px;}
.input-xxlarge{width:530px;}
input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{float:none;margin-left:0;}
input,textarea,.uneditable-input{margin-left:0;}
input.span12, textarea.span12, .uneditable-input.span12{width:930px;}
input.span11, textarea.span11, .uneditable-input.span11{width:850px;}
input.span10, textarea.span10, .uneditable-input.span10{width:770px;}
input.span9, textarea.span9, .uneditable-input.span9{width:690px;}
input.span8, textarea.span8, .uneditable-input.span8{width:610px;}
input.span7, textarea.span7, .uneditable-input.span7{width:530px;}
input.span6, textarea.span6, .uneditable-input.span6{width:450px;}
input.span5, textarea.span5, .uneditable-input.span5{width:370px;}
input.span4, textarea.span4, .uneditable-input.span4{width:290px;}
input.span3, textarea.span3, .uneditable-input.span3{width:210px;}
input.span2, textarea.span2, .uneditable-input.span2{width:130px;}
input.span1, textarea.span1, .uneditable-input.span1{width:50px;}
input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#eeeeee;border-color:#ddd;cursor:not-allowed;}
.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;}
.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;border-color:#c09853;}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;}
.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;}
.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;border-color:#b94a48;}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;}
.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;}
.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;border-color:#468847;}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;}
.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#eeeeee;border-top:1px solid #ddd;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";}
.form-actions:after{clear:both;}
.uneditable-input{display:block;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
:-moz-placeholder{color:#999999;}
::-webkit-input-placeholder{color:#999999;}
.help-block,.help-inline{color:#555555;}
.help-block{display:block;margin-bottom:9px;}
.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;}
.input-prepend,.input-append{margin-bottom:5px;}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{*margin-left:0;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{position:relative;z-index:2;}
.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;}
.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;min-width:16px;height:18px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #ffffff;vertical-align:middle;background-color:#eeeeee;border:1px solid #ccc;}
.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-prepend .active,.input-append .active{background-color:#a9dba9;border-color:#46a546;}
.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;}
.input-append input,.input-append select .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-append .uneditable-input{border-left-color:#eee;border-right-color:#ccc;}
.input-append .add-on,.input-append .btn{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.search-query{padding-left:14px;padding-right:14px;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;}
.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;margin-bottom:0;}
.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
.form-search label,.form-inline label{display:inline-block;}
.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;}
.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;}
.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-left:0;margin-right:3px;}
.control-group{margin-bottom:9px;}
legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;}
.form-horizontal .control-group{margin-bottom:18px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";}
.form-horizontal .control-group:after{clear:both;}
.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;}
.form-horizontal .controls{margin-left:160px;*display:inline-block;*margin-left:0;*padding-left:20px;}
.form-horizontal .help-block{margin-top:9px;margin-bottom:0;}
.form-horizontal .form-actions{padding-left:160px;}
table{max-width:100%;border-collapse:collapse;border-spacing:0;background-color:transparent;}
.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;}
.table th{font-weight:bold;}
.table thead th{vertical-align:bottom;}
.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
.table tbody+tbody{border-top:2px solid #dddddd;}
.table-condensed th,.table-condensed td{padding:4px 5px;}
.table-bordered{border:1px solid #dddddd;border-left:0;border-collapse:separate;*border-collapse:collapsed;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;}
.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;}
table .span1{float:none;width:44px;margin-left:0;}
table .span2{float:none;width:124px;margin-left:0;}
table .span3{float:none;width:204px;margin-left:0;}
table .span4{float:none;width:284px;margin-left:0;}
table .span5{float:none;width:364px;margin-left:0;}
table .span6{float:none;width:444px;margin-left:0;}
table .span7{float:none;width:524px;margin-left:0;}
table .span8{float:none;width:604px;margin-left:0;}
table .span9{float:none;width:684px;margin-left:0;}
table .span10{float:none;width:764px;margin-left:0;}
table .span11{float:none;width:844px;margin-left:0;}
table .span12{float:none;width:924px;margin-left:0;}
table .span13{float:none;width:1004px;margin-left:0;}
table .span14{float:none;width:1084px;margin-left:0;}
table .span15{float:none;width:1164px;margin-left:0;}
table .span16{float:none;width:1244px;margin-left:0;}
table .span17{float:none;width:1324px;margin-left:0;}
table .span18{float:none;width:1404px;margin-left:0;}
table .span19{float:none;width:1484px;margin-left:0;}
table .span20{float:none;width:1564px;margin-left:0;}
table .span21{float:none;width:1644px;margin-left:0;}
table .span22{float:none;width:1724px;margin-left:0;}
table .span23{float:none;width:1804px;margin-left:0;}
table .span24{float:none;width:1884px;margin-left:0;}
[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;*margin-right:.3em;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;}
.icon-white{background-image:url("../img/glyphicons-halflings-white.png");}
.icon-glass{background-position:0 0;}
.icon-music{background-position:-24px 0;}
.icon-search{background-position:-48px 0;}
.icon-envelope{background-position:-72px 0;}
.icon-heart{background-position:-96px 0;}
.icon-star{background-position:-120px 0;}
.icon-star-empty{background-position:-144px 0;}
.icon-user{background-position:-168px 0;}
.icon-film{background-position:-192px 0;}
.icon-th-large{background-position:-216px 0;}
.icon-th{background-position:-240px 0;}
.icon-th-list{background-position:-264px 0;}
.icon-ok{background-position:-288px 0;}
.icon-remove{background-position:-312px 0;}
.icon-zoom-in{background-position:-336px 0;}
.icon-zoom-out{background-position:-360px 0;}
.icon-off{background-position:-384px 0;}
.icon-signal{background-position:-408px 0;}
.icon-cog{background-position:-432px 0;}
.icon-trash{background-position:-456px 0;}
.icon-home{background-position:0 -24px;}
.icon-file{background-position:-24px -24px;}
.icon-time{background-position:-48px -24px;}
.icon-road{background-position:-72px -24px;}
.icon-download-alt{background-position:-96px -24px;}
.icon-download{background-position:-120px -24px;}
.icon-upload{background-position:-144px -24px;}
.icon-inbox{background-position:-168px -24px;}
.icon-play-circle{background-position:-192px -24px;}
.icon-repeat{background-position:-216px -24px;}
.icon-refresh{background-position:-240px -24px;}
.icon-list-alt{background-position:-264px -24px;}
.icon-lock{background-position:-287px -24px;}
.icon-flag{background-position:-312px -24px;}
.icon-headphones{background-position:-336px -24px;}
.icon-volume-off{background-position:-360px -24px;}
.icon-volume-down{background-position:-384px -24px;}
.icon-volume-up{background-position:-408px -24px;}
.icon-qrcode{background-position:-432px -24px;}
.icon-barcode{background-position:-456px -24px;}
.icon-tag{background-position:0 -48px;}
.icon-tags{background-position:-25px -48px;}
.icon-book{background-position:-48px -48px;}
.icon-bookmark{background-position:-72px -48px;}
.icon-print{background-position:-96px -48px;}
.icon-camera{background-position:-120px -48px;}
.icon-font{background-position:-144px -48px;}
.icon-bold{background-position:-167px -48px;}
.icon-italic{background-position:-192px -48px;}
.icon-text-height{background-position:-216px -48px;}
.icon-text-width{background-position:-240px -48px;}
.icon-align-left{background-position:-264px -48px;}
.icon-align-center{background-position:-288px -48px;}
.icon-align-right{background-position:-312px -48px;}
.icon-align-justify{background-position:-336px -48px;}
.icon-list{background-position:-360px -48px;}
.icon-indent-left{background-position:-384px -48px;}
.icon-indent-right{background-position:-408px -48px;}
.icon-facetime-video{background-position:-432px -48px;}
.icon-picture{background-position:-456px -48px;}
.icon-pencil{background-position:0 -72px;}
.icon-map-marker{background-position:-24px -72px;}
.icon-adjust{background-position:-48px -72px;}
.icon-tint{background-position:-72px -72px;}
.icon-edit{background-position:-96px -72px;}
.icon-share{background-position:-120px -72px;}
.icon-check{background-position:-144px -72px;}
.icon-move{background-position:-168px -72px;}
.icon-step-backward{background-position:-192px -72px;}
.icon-fast-backward{background-position:-216px -72px;}
.icon-backward{background-position:-240px -72px;}
.icon-play{background-position:-264px -72px;}
.icon-pause{background-position:-288px -72px;}
.icon-stop{background-position:-312px -72px;}
.icon-forward{background-position:-336px -72px;}
.icon-fast-forward{background-position:-360px -72px;}
.icon-step-forward{background-position:-384px -72px;}
.icon-eject{background-position:-408px -72px;}
.icon-chevron-left{background-position:-432px -72px;}
.icon-chevron-right{background-position:-456px -72px;}
.icon-plus-sign{background-position:0 -96px;}
.icon-minus-sign{background-position:-24px -96px;}
.icon-remove-sign{background-position:-48px -96px;}
.icon-ok-sign{background-position:-72px -96px;}
.icon-question-sign{background-position:-96px -96px;}
.icon-info-sign{background-position:-120px -96px;}
.icon-screenshot{background-position:-144px -96px;}
.icon-remove-circle{background-position:-168px -96px;}
.icon-ok-circle{background-position:-192px -96px;}
.icon-ban-circle{background-position:-216px -96px;}
.icon-arrow-left{background-position:-240px -96px;}
.icon-arrow-right{background-position:-264px -96px;}
.icon-arrow-up{background-position:-289px -96px;}
.icon-arrow-down{background-position:-312px -96px;}
.icon-share-alt{background-position:-336px -96px;}
.icon-resize-full{background-position:-360px -96px;}
.icon-resize-small{background-position:-384px -96px;}
.icon-plus{background-position:-408px -96px;}
.icon-minus{background-position:-433px -96px;}
.icon-asterisk{background-position:-456px -96px;}
.icon-exclamation-sign{background-position:0 -120px;}
.icon-gift{background-position:-24px -120px;}
.icon-leaf{background-position:-48px -120px;}
.icon-fire{background-position:-72px -120px;}
.icon-eye-open{background-position:-96px -120px;}
.icon-eye-close{background-position:-120px -120px;}
.icon-warning-sign{background-position:-144px -120px;}
.icon-plane{background-position:-168px -120px;}
.icon-calendar{background-position:-192px -120px;}
.icon-random{background-position:-216px -120px;}
.icon-comment{background-position:-240px -120px;}
.icon-magnet{background-position:-264px -120px;}
.icon-chevron-up{background-position:-288px -120px;}
.icon-chevron-down{background-position:-313px -119px;}
.icon-retweet{background-position:-336px -120px;}
.icon-shopping-cart{background-position:-360px -120px;}
.icon-folder-close{background-position:-384px -120px;}
.icon-folder-open{background-position:-408px -120px;}
.icon-resize-vertical{background-position:-432px -119px;}
.icon-resize-horizontal{background-position:-456px -118px;}
.dropdown{position:relative;}
.dropdown-toggle{*margin-bottom:-3px;}
.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
.caret{display:inline-block;width:0;height:0;vertical-align:top;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000000;opacity:0.3;filter:alpha(opacity=30);content:"";}
.dropdown .caret{margin-top:8px;margin-left:2px;}
.dropdown:hover .caret,.open.dropdown .caret{opacity:1;filter:alpha(opacity=100);}
.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;padding:4px 0;margin:0;list-style:none;background-color:#ffffff;border-color:#ccc;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:1px;-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;}.dropdown-menu.pull-right{right:0;left:auto;}
.dropdown-menu .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333333;white-space:nowrap;}
.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;}
.dropdown.open{*z-index:1000;}.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
.dropdown.open .dropdown-menu{display:block;}
.pull-right .dropdown-menu{left:auto;right:0;}
.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";}
.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
.collapse{-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;position:relative;overflow:hidden;height:0;}.collapse.in{height:auto;}
.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;opacity:0.4;filter:alpha(opacity=40);cursor:pointer;}
.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-ms-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(top, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);border:1px solid #cccccc;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);cursor:pointer;*margin-left:.3em;}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;}
.btn:active,.btn.active{background-color:#cccccc \9;}
.btn:first-child{*margin-left:0;}
.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.btn.active,.btn:active{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);background-color:#e6e6e6;background-color:#d9d9d9 \9;outline:0;}
.btn.disabled,.btn[disabled]{cursor:default;background-image:none;background-color:#e6e6e6;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.btn-large [class^="icon-"]{margin-top:1px;}
.btn-small{padding:5px 9px;font-size:11px;line-height:16px;}
.btn-small [class^="icon-"]{margin-top:-1px;}
.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;}
.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);color:#ffffff;}
.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);}
.btn-primary{background-color:#0074cc;background-image:-moz-linear-gradient(top, #0088cc, #0055cc);background-image:-ms-linear-gradient(top, #0088cc, #0055cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0055cc));background-image:-webkit-linear-gradient(top, #0088cc, #0055cc);background-image:-o-linear-gradient(top, #0088cc, #0055cc);background-image:linear-gradient(top, #0088cc, #0055cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0055cc', GradientType=0);border-color:#0055cc #0055cc #003580;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#0055cc;}
.btn-primary:active,.btn-primary.active{background-color:#004099 \9;}
.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;}
.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}
.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;}
.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;}
.btn-success:active,.btn-success.active{background-color:#408140 \9;}
.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;}
.btn-info:active,.btn-info.active{background-color:#24748c \9;}
.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top, #555555, #222222);background-image:-ms-linear-gradient(top, #555555, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));background-image:-webkit-linear-gradient(top, #555555, #222222);background-image:-o-linear-gradient(top, #555555, #222222);background-image:linear-gradient(top, #555555, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222222;}
.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}
button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;}
button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;}
button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;}
.btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.btn-group:after{display:table;content:"";}
.btn-group:after{clear:both;}
.btn-group:first-child{*margin-left:0;}
.btn-group+.btn-group{margin-left:5px;}
.btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;}
.btn-group .btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.btn-group .btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
.btn-group .btn:last-child,.btn-group .dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
.btn-group .btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
.btn-group .btn.large:last-child,.btn-group .large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
.btn-group .btn:hover,.btn-group .btn:focus,.btn-group .btn:active,.btn-group .btn.active{z-index:2;}
.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
.btn-group .dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:3px;*padding-bottom:3px;}
.btn-group .btn-mini.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:1px;*padding-bottom:1px;}
.btn-group .btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;}
.btn-group .btn-large.dropdown-toggle{padding-left:12px;padding-right:12px;}
.btn-group.open{*z-index:1000;}.btn-group.open .dropdown-menu{display:block;margin-top:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);}
.btn .caret{margin-top:7px;margin-left:0;}
.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);}
.btn-mini .caret{margin-top:5px;}
.btn-small .caret{margin-top:6px;}
.btn-large .caret{margin-top:6px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);}
.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#c09853;}
.alert-heading{color:inherit;}
.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;}
.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}
.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}
.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}
.alert-block{padding-top:14px;padding-bottom:14px;}
.alert-block>p,.alert-block>ul{margin-bottom:0;}
.alert-block p+p{margin-top:5px;}
.nav{margin-left:0;margin-bottom:18px;list-style:none;}
.nav>li>a{display:block;}
.nav>li>a:hover{text-decoration:none;background-color:#eeeeee;}
.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
.nav li+.nav-header{margin-top:9px;}
.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
.nav-list>li>a{padding:3px 15px;}
.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;}
.nav-list [class^="icon-"]{margin-right:2px;}
.nav-list .divider{height:1px;margin:8px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";}
.nav-tabs:after,.nav-pills:after{clear:both;}
.nav-tabs>li,.nav-pills>li{float:left;}
.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
.nav-tabs{border-bottom:1px solid #ddd;}
.nav-tabs>li{margin-bottom:-1px;}
.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#0088cc;}
.nav-stacked>li{float:none;}
.nav-stacked>li>a{margin-right:0;}
.nav-tabs.nav-stacked{border-bottom:0;}
.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
.nav-tabs .dropdown-menu,.nav-pills .dropdown-menu{margin-top:1px;border-width:1px;}
.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;}
.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580;}
.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;}
.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;}
.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
.nav .open .caret,.nav .open.active .caret,.nav .open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
.tabs-stacked .open>a:hover{border-color:#999999;}
.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";}
.tabbable:after{clear:both;}
.tab-content{display:table;width:100%;}
.tabs-below .nav-tabs,.tabs-right .nav-tabs,.tabs-left .nav-tabs{border-bottom:0;}
.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
.tab-content>.active,.pill-content>.active{display:block;}
.tabs-below .nav-tabs{border-top:1px solid #ddd;}
.tabs-below .nav-tabs>li{margin-top:-1px;margin-bottom:0;}
.tabs-below .nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below .nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
.tabs-below .nav-tabs .active>a,.tabs-below .nav-tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;}
.tabs-left .nav-tabs>li,.tabs-right .nav-tabs>li{float:none;}
.tabs-left .nav-tabs>li>a,.tabs-right .nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
.tabs-left .nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
.tabs-left .nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
.tabs-left .nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
.tabs-left .nav-tabs .active>a,.tabs-left .nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
.tabs-right .nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
.tabs-right .nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
.tabs-right .nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
.tabs-right .nav-tabs .active>a,.tabs-right .nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
.navbar{*position:relative;*z-index:2;overflow:visible;margin-bottom:18px;}
.navbar-inner{padding-left:20px;padding-right:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
.navbar .container{width:auto;}
.btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:dximagetransform.microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);}.btn-navbar:hover,.btn-navbar:active,.btn-navbar.active,.btn-navbar.disabled,.btn-navbar[disabled]{background-color:#222222;}
.btn-navbar:active,.btn-navbar.active{background-color:#080808 \9;}
.btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
.nav-collapse.collapse{height:auto;}
.navbar{color:#999999;}.navbar .brand:hover{text-decoration:none;}
.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;}
.navbar .navbar-text{margin-bottom:0;line-height:40px;}
.navbar .btn,.navbar .btn-group{margin-top:5px;}
.navbar .btn-group .btn{margin-top:0;}
.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";}
.navbar-form:after{clear:both;}
.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;}
.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#626262;border:1px solid #151515;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query:-moz-placeholder{color:#cccccc;}
.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;}
.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
.navbar-fixed-top{top:0;}
.navbar-fixed-bottom{bottom:0;}
.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
.navbar .nav.pull-right{float:right;}
.navbar .nav>li{display:block;float:left;}
.navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
.navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;}
.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;}
.navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;}
.navbar .nav.pull-right{margin-left:10px;margin-right:0;}
.navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
.navbar-fixed-bottom .dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
.navbar-fixed-bottom .dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
.navbar .nav .dropdown-toggle .caret,.navbar .nav .open.dropdown .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
.navbar .nav .active .caret{opacity:1;filter:alpha(opacity=100);}
.navbar .nav .open>.dropdown-toggle,.navbar .nav .active>.dropdown-toggle,.navbar .nav .open.active>.dropdown-toggle{background-color:transparent;}
.navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;}
.navbar .nav.pull-right .dropdown-menu,.navbar .nav .dropdown-menu.pull-right{left:auto;right:0;}.navbar .nav.pull-right .dropdown-menu:before,.navbar .nav .dropdown-menu.pull-right:before{left:auto;right:12px;}
.navbar .nav.pull-right .dropdown-menu:after,.navbar .nav .dropdown-menu.pull-right:after{left:auto;right:13px;}
.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}
.breadcrumb .divider{padding:0 5px;color:#999999;}
.breadcrumb .active a{color:#333333;}
.pagination{height:36px;margin:18px 0;}
.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
.pagination li{display:inline;}
.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;}
.pagination a:hover,.pagination .active a{background-color:#f5f5f5;}
.pagination .active a{color:#999999;cursor:default;}
.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;}
.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
.pagination-centered{text-align:center;}
.pagination-right{text-align:right;}
.pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";}
.pager:after{clear:both;}
.pager li{display:inline;}
.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
.pager a:hover{text-decoration:none;background-color:#f5f5f5;}
.pager .next a{float:right;}
.pager .previous a{float:left;}
.pager .disabled a,.pager .disabled a:hover{color:#999999;background-color:#fff;cursor:default;}
.modal-open .dropdown-menu{z-index:2050;}
.modal-open .dropdown.open{*z-index:2050;}
.modal-open .popover{z-index:2060;}
.modal-open .tooltip{z-index:2070;}
.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
.modal.fade.in{top:50%;}
.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
.modal-body{overflow-y:auto;max-height:400px;padding:15px;}
.modal-form{margin-bottom:0;}
.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";}
.modal-footer:after{clear:both;}
.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
.tooltip.top{margin-top:-2px;}
.tooltip.right{margin-left:2px;}
.tooltip.bottom{margin-top:2px;}
.tooltip.left{margin-left:-2px;}
.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.tooltip-arrow{position:absolute;width:0;height:0;}
.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;}
.popover.right{margin-left:5px;}
.popover.bottom{margin-top:5px;}
.popover.left{margin-left:-5px;}
.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
.popover .arrow{position:absolute;width:0;height:0;}
.popover-inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;}
.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";}
.thumbnails:after{clear:both;}
.thumbnails>li{float:left;margin:0 0 18px 20px;}
.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}
a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
.thumbnail .caption{padding:9px;}
.label{padding:1px 4px 2px;font-size:10.998px;font-weight:bold;line-height:13px;color:#ffffff;vertical-align:middle;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
.label:hover{color:#ffffff;text-decoration:none;}
.label-important{background-color:#b94a48;}
.label-important:hover{background-color:#953b39;}
.label-warning{background-color:#f89406;}
.label-warning:hover{background-color:#c67605;}
.label-success{background-color:#468847;}
.label-success:hover{background-color:#356635;}
.label-info{background-color:#3a87ad;}
.label-info:hover{background-color:#2d6987;}
.label-inverse{background-color:#333333;}
.label-inverse:hover{background-color:#1a1a1a;}
.badge{padding:1px 9px 2px;font-size:12.025px;font-weight:bold;white-space:nowrap;color:#ffffff;background-color:#999999;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}
.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;}
.badge-error{background-color:#b94a48;}
.badge-error:hover{background-color:#953b39;}
.badge-warning{background-color:#f89406;}
.badge-warning:hover{background-color:#c67605;}
.badge-success{background-color:#468847;}
.badge-success:hover{background-color:#356635;}
.badge-info{background-color:#3a87ad;}
.badge-info:hover{background-color:#2d6987;}
.badge-inverse{background-color:#333333;}
.badge-inverse:hover{background-color:#1a1a1a;}
@-webkit-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.progress .bar{width:0%;height:18px;color:#ffffff;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);}
.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);}
.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);}
.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.progress-warning .bar{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);}
.progress-warning.progress-striped .bar{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
.accordion{margin-bottom:18px;}
.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
.accordion-heading{border-bottom:0;}
.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
.carousel{position:relative;margin-bottom:18px;line-height:1;}
.carousel-inner{overflow:hidden;width:100%;position:relative;}
.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}
.carousel .item>img{display:block;line-height:1;}
.carousel .active,.carousel .next,.carousel .prev{display:block;}
.carousel .active{left:0;}
.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;}
.carousel .next{left:100%;}
.carousel .prev{left:-100%;}
.carousel .next.left,.carousel .prev.right{left:0;}
.carousel .active.left{left:-100%;}
.carousel .active.right{left:100%;}
.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);}
.carousel-caption h4,.carousel-caption p{color:#ffffff;}
.hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;}
.pull-right{float:right;}
.pull-left{float:left;}
.hide{display:none;}
.show{display:block;}
.invisible{visibility:hidden;}
+172
View File
@@ -0,0 +1,172 @@
img.AngularJS-small {
width: 95px;
height: 25px;
}
.clear-navbar {
margin-top: 60px;
}
.footer {
padding-top: 2em;
background-color: #333;
color: white;
padding-bottom: 2em;
}
.spacer {
height: 1em;
}
/* =============================== */
.form-search .dropdown-menu {
margin-left: 10px;
}
.form-search .code {
font-family: monospace;
font-weight: bold;
font-size: 13px;
color: black;
}
.form-search > ul.nav > li.module {
background-color: #d3d3d3;
}
.form-search > ul.nav > li.section {
background-color: #ebebeb;
min-height: 14px;
}
.form-search > ul.nav > li.last {
margin-bottom: 1em;
}
.form-search .well {
border-color: #d3d3d3;
padding-top: 0;
padding-bottom: 0;
margin-bottom: 15px;
}
.form-search .well .nav-header {
text-transform: none;
margin-top: 0;
margin-left: -15px;
margin-right: -15px;
}
.form-search .well .nav-header a {
text-transform: none;
color: black;
}
.form-search .well .nav-header a:hover {
background-color: inherit;
}
.form-search .well li {
line-height: 14px;
}
.form-search .well .guide {
float: right;
padding-top: 0;
color: gray;
}
/* =============================== */
/* Content */
/* =============================== */
.hint {
font-size: .7em;
color: #c0c0c0;
}
.content code {
background-color: inherit;
color: inherit;
border: none;
padding: 0;
font-size: inherit;
font-family: monospace;
}
.content h2,
.content h3,
.content h4,
.content h5 {
margin-top: 1em;
}
ul.parameters > li > p,
.returns > p {
display: inline;
}
ul.methods > li,
ul.properties > li,
ul.events > li {
list-style: none;
min-height: 20px;
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #eee;
border: 1px solid rgba(0, 0, 0, 0.05);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
}
.member.method > h2,
.member.property > h2,
.member.event > h2 {
margin-bottom: .5em;
}
ul.methods > li > h3,
ul.properties > li > h3,
ul.events > li > h3 {
margin: -19px -19px 1em -19px;
padding: .25em 19px;
background-color: #d3d3d3;
font-family: monospace;
}
.diagram {
display: block;
margin: 2em auto;
padding: 1em;
border: 1px solid black;
-moz-box-shadow: 4px 4px 6px #48577D;
-webkit-box-shadow: 4px 4px 6px #48577D;
box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius: 15px;
}
.tutorial-nav {
margin-left: 175px;
color: black;
margin-top: 2em;
margin-bottom: 2em;
}
.tutorial-nav a {
color: white;
}
.tutorial-nav a:hover {
color: white;
text-decoration: none;
}
+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>
-520
View File
@@ -1,520 +0,0 @@
@charset "UTF-8";
body {
background: #000 url("img/texture_1.png");
}
/*----- Layout Generic Styles -----*/
body,td,th {
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
color: #000;
margin: 0;
}
a:link, a:visited, a:hover {
color: #5d6db6;
text-decoration: none;
}
a:active {
text-decoration: none;
}
p {
font-size: 14px;
padding: 0.1em 1em;
line-height: 1.4em;
}
li > p {
padding-left: 0;
}
h2 {
margin: 1.5em 0 1em 0;
}
h3 {
margin: 1.8em 0 1em 0;
}
h4 {
margin: 2em 0 1em 0;
}
li {
margin: 0.3em 0 0.3em 0;
}
.member {
border: 1px solid gray;
margin: 2em 0;
}
.member p {
padding: 0;
}
.member>h2 {
background-color: lightgray;
padding: .2em .4em;
margin: 0;
border-bottom: 1px solid gray;
}
.member>ul {
list-style: none;
padding: 0;
}
.member>ul>li {
border: 1px solid lightgray;
margin: 1em;
}
.member>ul>li>h3 {
background-color: #EEE;
padding: .2em .4em;
margin: 0;
font-family: monospace;
border-bottom: 1px solid lightgray;
}
.member>ul>li>div {
padding: 1em;
}
/*----- Upgrade IE Prompt -----*/
#oldIePrompt {
margin: 0 auto;
background-color: red;
color: black;
text-align: center;
padding: 5px 0;
}
#oldIePrompt a,
#oldIePrompt a:visited {
color: yellow;
}
/*----- Global Layout -----*/
#loading {
position: fixed;
left: 50%;
margin: 1.5em -40px;
width: 80px;
padding: 0.2em 0.2em;
text-align: center;
font-weight: bold;
font-size: 13px;
color: #FFFFFF;
border: 1px solid black;
background-color: #7989D6;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
-webkit-box-shadow: 0 2px 8px rgba(0,0,0,0.6);
-moz-box-shadow: 0 2px 8px rgba(0,0,0,0.6);
box-shadow: 0 2px 8px rgba(0,0,0,0.6);
z-index: 2; /* just below #fader */
}
#container {
width: 1150px;
margin: 0 auto;
background: #F8AC09 url(img/yellow_bkgnd.jpg) no-repeat top left;
}
#footer {
clear: both;
padding: 2em 4em 1em;
font-size: 12px;
}
#offline{
text-decoration:underline;
cursor:pointer;
color:blue;
}
#copyright {
float:right;
text-align: right;
}
#version {
margin-top: -1.2em;
margin-left: -2em;
margin-right: 0.5em;
color: #251BE0;
}
/*----- navigation styles -----*/
#navbar {
margin: 0;
padding: 0;
list-style-type: none;
background: #7989D6;
height: 3.5em;
border-bottom: 4px solid #808080;
}
#navbar li {
display: inline;
}
#navbar a:link, #navbar a:visited {
font-size: 1.2em;
color: #FFF;
font-weight: bold;
text-decoration: none;
text-align: center;
display: inline-block;
width: 11em;
margin-top: 0.4em;
padding: 0.5em 0;
}
#navbar a:hover {
color: #000;
}
#navbar a.current {
background-color: #333;
-webkit-border-radius:10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 6px #48577D;
-moz-box-shadow: 4px 4px 6px #48577D;
box-shadow: 4px 4px 6px #48577D;
}
#navbar a.current:hover {
color: #fff;
}
/*------ sidebar styles ------*/
#sidebar {
margin-top: 4em;
width: 15.5em;
padding: 0.8em 0.7em 0.7em 0.7em;
background: #7989D6;
overflow: hidden;
float: left;
-moz-box-shadow: 4px 4px 6px #48577D;
-webkit-box-shadow: 4px 4px 6px #48577D;
box-shadow: 4px 4px 6px #48577D;
-moz-border-radius-topright: 15px;
-webkit-border-top-right-radius: 15px;
border-top-right-radius: 15px;
-moz-border-radius-bottomright: 15px;
-webkit-border-bottom-right-radius: 15px;
border-bottom-right-radius: 15px;
}
#sidebar input {
width: 207px;
}
#sidebar #content-list.tutorial {
list-style: none;
text-indent: -1.2em;
}
#sidebar #content-list.tutorial .level-0 {
text-indent: 0;
}
#content-list {
background: #fff;
padding: 1em 0.4em 1em 2em;
margin: 0.95em -0.95em -1em -0.6em;
line-height: 1.5em;
}
#content-list .level-0 {
font-size: 1.3em;
list-style: none;
margin-left: -1.2em;
padding-bottom: 0.2em;
}
#content-list .level-1 {
margin-left: 0em;
}
#content-list .level-2 {
margin-left: 1em;
}
#content-list .level-3 {
margin-left: 2em;
}
#content-list .level-4 {
margin-left: 3em;
}
#content-list .level-5 {
margin-left: 4em;
}
#content-list .level-6 {
margin-left: 5em;
}
#content-list a.current {
font-weight: bold;
color: #000;
text-decoration: none;
}
/*----- content styles -----*/
.content-panel {
float: right;
margin-top: 4em;
margin-right: 3em;
width: 57em;
padding: 0.5em 1em 1em 1em;
background: #7989D6;
overflow: hidden;
-moz-box-shadow: 4px 4px 6px #48577D;
-webkit-box-shadow: 4px 4px 6px #48577D;
box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius: 15px;
}
.content-panel > h2 {
font-size: 2em;
font-weight: normal;
color: #fff;
margin: 0 4em 0 0;
}
.content-panel-content {
background-color: #fff;
display: block;
margin: 0.7em -1em -1em;
padding: 0.5em 1.5em;
}
#content > h1 {
display: none;
}
#content img:not(.ng-directive) {
/* the negation rule above is to avoid applying this rule to images in buzz and other examples */
display: block;
margin: 2em auto;
padding: 1em;
border: 1px solid black;
-moz-box-shadow: 4px 4px 6px #48577D;
-webkit-box-shadow: 4px 4px 6px #48577D;
box-shadow: 4px 4px 6px #48577D;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
border-radius: 15px;
}
#content pre {
padding-left: 1.5em;
}
#content .syntaxhighlighter {
margin: 1.5em 0 1.5em 0 !important;
}
#content .parameters p:nth-child(2) {
display: inline;
padding-left: 0;
}
#content .returns p:nth-child(2) {
display: inline;
padding-left: 0;
}
#disqus #disqus_thread {
margin: 0.7em -1em -1em;
}
/* subpages */
#fader {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.8;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
z-index: 3;
}
#subpage > div {
position: fixed;
top: 50%;
left: 50%;
width: 729px;
margin-top: -140px;
margin-left: -365px;
z-index: 3;
background-color: #7989D6;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: 4px 4px 6px #48577D;
-webkit-box-shadow: 4px 4px 6px #48577D;
-moz-box-shadow: 4px 4px 6px #48577D;
}
#subpage h2 {
height: 1.8em;
-moz-border-radius-topright: 15px;
-moz-border-radius-topleft: 15px;
border-radius-topright: 15px;
border-radius-topleft: 15px;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
border-top-right-radius: 15px;
border-top-left-radius: 15px;
padding: 0.6em 0 0 1em;
margin: 0;
color: white;
}
#subpage > div > a {
color: black;
float: right;
margin: -40px 10px;
font-size: 1em;
}
#subpage > div > a:hover {
text-decoration: none;
color: white;
}
#subpage > div > p {
background-color: white;
padding: 0.5em 1em 0.5em 1em;
margin-bottom: 0.5em;
}
#cacheButton {
margin: 0em 2em 1em 0em;
float:right;
}
.table {
border-collapse: collapse;
}
.table th:first-child {
text-align: right;
}
.table th,
.table td {
border: 1px solid black;
padding: .5em 1em;
}
.table th {
white-space: nowrap;
}
.table th.section {
text-align: left;
background-color: lightgray;
}
[ng\:cloak], .ng-cloak {
display: none;
}
.inline * {
display: inline;
}
table {
border-collapse: collapse;
}
td {
padding: 4px;
border: 1px solid lightgray;
}
td.head, thead td, .head td {
background-color: #EEE;
}
td.empty-corner-lt {
border-left: 0;
border-top: 0;
background-color: transparent;
}
/* service.$location Html5 mode example */
.html5-hashbang-example div input {
width: 360px;
}
.doc-example-live .error {
color: red;
}
.odd {
background-color: #808080;
}
.even {
background-color: #d3d3d3;
}
-163
View File
@@ -1,163 +0,0 @@
DocsController.$inject = ['$scope', '$location', '$window', '$cookies', '$filter'];
function DocsController(scope, $location, $window, $cookies, $filter) {
window.$root = scope.$root;
var OFFLINE_COOKIE_NAME = 'ng-offline',
DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/,
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/,
filter = $filter('filter');
scope.$location = $location;
scope.versionNumber = angular.version.full;
scope.version = angular.version.full + " " + angular.version.codeName;
scope.subpage = false;
scope.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
scope.futurePartialTitle = null;
scope.loading = 0;
if (!$location.path() || INDEX_PATH.test($location.path())) {
$location.path('/api').replace();
}
scope.$watch('$location.path()', function(path) {
// ignore non-doc links which are used in examples
if (DOCS_PATH.test(path)) {
var parts = path.split('/');
scope.sectionId = parts[1];
scope.partialId = parts[2] || 'index';
scope.pages = filter(NG_PAGES, {section: scope.sectionId});
var i = scope.pages.length;
while (i--) {
if (scope.pages[i].id == scope.partialId) break;
}
if (i<0) {
scope.partialTitle = 'Error: Page Not Found!';
delete scope.partialId;
} else {
// TODO(i): this is not ideal but better than updating the title before a partial arrives,
// which results in the old partial being displayed with the new title
scope.futurePartialTitle = scope.pages[i].name;
scope.loading++;
}
}
});
scope.getUrl = function(page) {
return page.section + (page.id == 'index' ? '' : '/' + page.id);
};
scope.getCurrentPartial = function() {
return this.partialId
? ('./partials/' + this.sectionId + '/' + this.partialId.replace('angular.Module', 'angular.IModule') + '.html')
: '';
};
scope.getClass = function(page) {
var depth = page.depth,
cssClass = 'level-' + depth + (page.name == this.partialId ? ' selected' : '');
if (page.section == 'api')
cssClass += ' monospace';
return cssClass;
};
scope.selectedSection = function(section) {
return section == scope.sectionId ? 'current' : '';
};
scope.selectedPartial = function(partial) {
return partial.id == scope.partialId ? 'current' : '';
};
scope.afterPartialLoaded = function() {
var currentPageId = $location.path();
scope.loading--;
scope.partialTitle = scope.futurePartialTitle;
SyntaxHighlighter.highlight();
$window._gaq.push(['_trackPageview', currentPageId]);
loadDisqus(currentPageId);
};
/** stores a cookie that is used by apache to decide which manifest ot send */
scope.enableOffline = function() {
//The cookie will be good for one year!
var date = new Date();
date.setTime(date.getTime()+(365*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
var value = angular.version.full;
document.cookie = OFFLINE_COOKIE_NAME + "="+value+expires+"; path=" + $location.path;
//force the page to reload so server can serve new manifest file
window.location.reload(true);
};
// bind escape to hash reset callback
angular.element(window).bind('keydown', function(e) {
if (e.keyCode === 27) {
scope.$apply(function() {
scope.subpage = false;
});
}
});
function loadDisqus(currentPageId) {
// http://docs.disqus.com/help/2/
window.disqus_shortname = 'angularjs';
window.disqus_identifier = currentPageId;
window.disqus_url = 'http://docs-next.angularjs.org' + currentPageId;
if ($location.host() == 'localhost') {
return; // don't display disqus on localhost, comment this out if needed
//window.disqus_developer = 1;
}
// http://docs.disqus.com/developers/universal/
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://angularjs.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
angular.element(document.getElementById('disqus_thread')).html('');
}
}
SyntaxHighlighter['defaults'].toolbar = false;
SyntaxHighlighter['defaults'].gutter = true;
/**
* Controller for tutorial instructions
* @param $cookieStore
* @constructor
*/
function TutorialInstructionsCtrl($cookieStore) {
this.selected = $cookieStore.get('selEnv') || 'git-mac';
this.currentCls = function(id, cls) {
return this.selected == id ? cls || 'current' : '';
};
this.select = function(id) {
this.selected = id;
$cookieStore.put('selEnv', id);
};
}
angular.module('ngdocs', ['ngdocs.directives'], function($locationProvider, $filterProvider, $compileProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$filterProvider.register('title', function(){
return function(text) {
return text && text.replace(/^angular\.module\.([^\.]+)(\.(.*))?$/, function(_, module, _0, name){
return 'Module ' + module + (name ? ' - ' + name : '');
});
};
});
$compileProvider.directive('code', function() {
return { restrict: 'E', terminal: true };
});
});
+226 -66
View File
@@ -1,13 +1,17 @@
<!doctype html>
<html xmlns:ng="http://angularjs.org/"
xmlns:doc="http://docs.angularjs.org/"
id="ng:app" ng:app="ngdocs"
ng:controller="DocsController"
doc:manifest>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7 ng-app: docsApp;" lang="en" ng-controller="DocsController"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8 ng-app: docsApp;" lang="en" ng-controller="DocsController"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9 ng-app: docsApp;" lang="en" ng-controller="DocsController"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js ng-app: docsApp;" lang="en" ng-controller="DocsController"> <!--<![endif]-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="Description"
content="AngularJS is what HTML would have been, had it been designed for building web-apps.
Declarative templates with data-binding, MVC, dependency injection and great
testability story all implemented with pure client-side JavaScript!">
<meta name="fragment" content="!">
<title ng:bind-template="AngularJS: {{partialTitle | title}}">AngularJS</title>
<title ng-bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
<script type="text/javascript">
// dynamically add base tag as well as css and javascript files.
// we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources
@@ -18,19 +22,26 @@
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;
addTag('base', {href: baseUrl});
addTag('link', {rel: 'stylesheet', href: 'docs-combined.css', type: 'text/css'});
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);
addTag('script', {src: 'docs-combined.js'}, sync);
addTag('script', {src: 'docs-keywords.js'}, sync);
addTag('link', {rel: 'stylesheet', href: 'css/bootstrap.min.css', type: 'text/css'});
addTag('link', {rel: 'stylesheet', href: 'http://angularjs.org/css/font-awesome.css', type: 'text/css'});
addTag('link', {rel: 'stylesheet', href: 'css/docs.css', type: 'text/css'});
if (jQuery) addTag('script', {src: debug ? 'js/jquery.js' : '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: path('angular-bootstrap.js') }, sync);
addTag('script', {src: path('angular-bootstrap-prettify.js') }, sync);
addTag('script', {src: 'js/docs.js'}, sync);
addTag('script', {src: 'js/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),
@@ -80,70 +91,193 @@
</head>
<body>
<div id="container">
<!--[if lt IE 9]>
<div id="oldIePrompt">
You are using an old version of Internet Explorer.
For better and safer browsing experience please <a href="http://www.microsoft.com/IE9">upgrade IE</a>
or install <a href="http://google.com/chrome">Google Chrome browser</a>.
<header class="header">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="http://angularjs.org" style="padding-top: 6px; padding-bottom: 0px;">
<img class="AngularJS-small" src="http://angularjs.org/img/AngularJS-small.png">
</a>
<ul class="nav">
<li class="divider-vertical"></li>
<li><a href="http://angularjs.org"><i class="icon-home icon-white"></i> Home</a></li>
<li class="divider-vertical"></li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-eye-open icon-white"></i> Learn <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li class="disabled"><a href="">Why AngularJS?</a></li>
<li><a href="http://www.youtube.com/user/angularjs">Watch</a></li>
<li><a href="tutorial">Tutorial</a></li>
<li><a href="https://github.com/angular/angular.js/wiki/Projects-using-AngularJS">Case Studies</a></li>
<li><a href="misc/faq">FAQ</a></li>
</ul>
</li>
<li class="divider-vertical"></li>
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-book icon-white"></i> Develop <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="tutorial">Tutorial</a></li>
<li><a href="guide/">Developer Guide</a></li>
<li><a href="api/">API Reference</a></li>
<li><a href="misc/contribute">Contribute</a></li>
<li><a href="http://code.angularjs.org/">Download</a></li>
</ul>
</li>
<li class="divider-vertical"></li>
<li class="dropdown">
<a href="" class="dropdown-toggle" data-toggle="dropdown">
<i class="icon-comment icon-white"></i> Discuss <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><a href="http://groups.google.com/group/angular">Mailing List</a></li>
<li><a href="http://webchat.freenode.net/?channels=angularjs&uio=d4">Chat Room</a></li>
<li class="divider"></li>
<li><a href="https://twitter.com/#!/angularjs">Twitter</a></li>
<li><a href="https://plus.google.com/110323587230527980117">Google+</a></li>
<li class="divider"></li>
<li><a href="https://github.com/angular/angular.js">GitHub</a></li>
<li><a href="https://github.com/angular/angular.js/issues">Issue Tracker</a></li>
</ul>
</li>
<li class="divider-vertical"></li>
</ul>
<form class="navbar-search pull-right" method="GET" action="https://www.google.com/search">
<input type="text" name="as_q" class="search-query" placeholder="Search">
<input type="hidden" name="as_sitesearch" value="angularjs.org">
</form>
</div>
</div>
<![endif]-->
</div>
</header>
<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="api" ng:class="selectedSection('api')">API Reference</a></li>
<li><a href="cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
<li><a href="guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
</ul>
<div role="main" class="container">
<div class="row clear-navbar"></div>
<div id="sidebar">
<input type="text" ng:model="search" id="search-box" placeholder="search the docs"
tabindex="1" accesskey="s" ng:model-instant>
<div class="row">
<div class="span12">
<!--[if lt IE 7]>
<p class="alert alert-error">Your browser is <em>ancient!</em>
<a href="http://browsehappy.com/">Upgrade to a different browser</a> or
<a href="http://www.google.com/chromeframe/?redirect=true">install Google Chrome Frame</a> to
experience this site.
</p>
<![endif]-->
<!--[if lt IE 9]>
<div class="alert">
You are using an old version of Internet Explorer.
For better and safer browsing experience please <a href="http://www.microsoft.com/IE9">upgrade IE</a>
or install <a href="http://google.com/chrome">Google Chrome browser</a>.
</div>
<![endif]-->
</div>
<ul id="content-list" ng:class="sectionId" ng:cloak>
<li ng:repeat="page in pages | filter:search" ng:class="getClass(page)">
<a href="{{getUrl(page)}}" ng:class="selectedPartial(page)"
ng:bind="page.shortName"
tabindex="2"></a>
</li>
</ul>
</div>
<div id="loading" ng:show="loading">Loading...</div>
<div class="row">
<div class="span3">
<form class="well form-search" ng-submit="submitForm()">
<div class="dropdown search"
ng-class="{open: focused && bestMatch.rank > 0 && bestMatch.page != currentPage}">
<input type="text" ng-model="search" placeholder="search the docs"
tabindex="1" accesskey="s" class="input-medium search-query" focused="focused">
<ul class="dropdown-menu">
<li>
<a href="{{bestMatch.page.url}}">{{bestMatch.page.shortName}}</a>
</li>
</ul>
</div>
<div class="spacer"></div>
<div ng-show="search">Filtered results:</div>
<ul class="nav nav-list" ng-hide="page">
<li ng-repeat="page in pages" ng-class="navClass(page)">
<a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>
</li>
</ul>
<div class="content-panel">
<h2 ng:bind="partialTitle | title"></h2>
<ng:include id="content" class="content-panel-content" autoscroll
src="getCurrentPartial()" onload="afterPartialLoaded()"></ng:include>
</div>
<ul class="nav nav-list well" ng-repeat="module in modules">
<li class="nav-header module">
<a class="guide" href="{{URL.module}}">module</a>
<a class="code" href="{{module.url}}">{{module.name}}</a>
</li>
<div id="disqus" class="content-panel">
<h2>Discussion</h2>
<div id="disqus_thread" class="content-panel-content"></div>
</div>
<li class="nav-header section" ng-show="module.directives">
<a href="{{URL.directive}}" class="guide">directive</a>
</li>
<li ng-repeat="page in module.directives" ng-class="navClass(page)">
<a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>
</li>
<div id="footer" ng:cloak>
<a id="version"
ng:href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
ng:bind-template="v{{version}}">
</a>
<!-- TODO(i): enable
<a ng:hide="offlineEnabled" ng:click ="subpage = true">(enable offline support)</a>
<span ng:show="offlineEnabled">(offline support enabled)</span>
-->
<span id="copyright">© 2010-2012 AngularJS</span>
<li class="nav-header section" ng-show="module.filters">
<a href="{{URL.filter}}" class="guide">filter</a>
</li>
<li ng-repeat="page in module.filters" ng-class="navClass(page)">
<a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>
</li>
<li class="nav-header section" ng-show="module.services">
<a href="{{URL.service}}" class="guide">service</a>
</li>
<li ng-repeat="service in module.services" ng-class="navClass(service.instance, service.provider)">
<a ng-show="service.provider" class="pull-right" href="{{service.provider.url}}" tabindex="2"><i class="icon-cog"></i></a>
<a href="{{service.instance.url}}" tabindex="2">{{service.name}}</a>
</li>
<li class="nav-header section" ng-show="module.types">
<a href="{{URL.type}}" class="guide">Types</a>
</li>
<li ng-repeat="page in module.types" ng-class="navClass(page)">
<a href="{{page.url}}" tabindex="2">{{page.shortName}}</a>
</li>
<li class="nav-header section" ng-show="module.globals">
<a href="{{URL.api}}" class="global guide">global APIs</a>
&nbsp;
</li>
<li ng-repeat="page in module.globals" ng-class="navClass(page)">
<a href="{{page.url}}" tabindex="2">{{page.id}}</a>
</li>
</ul>
</form>
</div>
<div class="span9">
<ul class="breadcrumb">
<li ng-repeat="crumb in breadcrumb">
<span ng-hide="crumb.url">{{crumb.name}}</span>
<a ng-show="crumb.url" href="{{crumb.url}}">{{crumb.name}}</a>
<span ng-show="crumb.url" class="divider">/</span>
</li>
</ul>
<div id="loading" ng-show="loading">Loading...</div>
<div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content"></div>
<div id="disqus" class="disqus">
<h2>Discussion</h2>
<div id="disqus_thread" class="content-panel-content"></div>
</div>
</div>
</div>
</div>
<div id="fader" ng:show="subpage" style="display: none"></div>
<div id="subpage" ng:show="subpage" style="display: none">
<div id="fader" ng-show="subpage" style="display: none"></div>
<div id="subpage" ng-show="subpage" style="display: none">
<div>
<h2>Would you like full offline support for this AngularJS Docs App?</h2>
<a ng:click="subpage=false">&#10005;</a>
<a ng-click="subpage=false">&#10005;</a>
<p>
If you want to be able to access the entire AngularJS documentation offline, click the
button below. This will reload the current page and trigger background downloads of all the
@@ -153,8 +287,34 @@
This feature is supported on all modern browsers, except for IE9 which lacks application
cache support.
</p>
<button id="cacheButton" ng:click="enableOffline()">Let me have them all!</button>
<button id="cacheButton" ng-click="enableOffline()">Let me have them all!</button>
</div>
</div>
<footer class="footer">
<div class="container">
<p class="pull-right"><a href="#">Back to top</a></p>
<p>
Super-powered by Google ©2010-2012
( <a id="version"
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
ng-bind-template="v{{version}}">
</a>
<!-- TODO(i): enable
<a ng-hide="offlineEnabled" ng-click ="subpage = true">(enable offline support)</a>
<span ng-show="offlineEnabled">(offline support enabled)</span>
-->
)
</p>
<p>
Code licensed under the
<a href="https://github.com/angular/angular.js/blob/master/LICENSE" target="_blank">The
MIT License</a>. Documentation licensed under <a
href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.
</p>
</div>
</footer>
</body>
</html>
-1
View File
@@ -1 +0,0 @@
../../../lib/jquery/jquery.min.js
@@ -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');
File diff suppressed because one or more lines are too long
+516
View File
@@ -0,0 +1,516 @@
var docsApp = {
controller: {},
directive: {},
serviceFactory: {}
};
docsApp.directive.focused = function($defer) {
return function(scope, element, attrs) {
element[0].focus();
element.bind('focus', function() {
scope.$apply(attrs.focused + '=true');
});
element.bind('blur', function() {
// have to use defer, so that we close the drop-down after the user clicks,
// otherwise when the user clicks we process the closing before we process the click.
$defer(attrs.focused + '=false');
});
scope.$eval(attrs.focused + '=true')
}
};
docsApp.directive.code = function() {
return { restrict:'E', terminal: true };
};
docsApp.directive.sourceEdit = function(getEmbeddedTemplate) {
return {
template: '<button ng-click="fiddle($event)" class="btn btn-primary pull-right"><i class="icon-pencil icon-white"></i> Edit</button>\n',
scope: true,
controller: function($scope, $attrs, openJsFiddle) {
var sources = {
module: $attrs.sourceEdit,
deps: read($attrs.sourceEditDeps),
html: read($attrs.sourceEditHtml),
css: read($attrs.sourceEditCss),
js: read($attrs.sourceEditJs),
unit: read($attrs.sourceEditUnit),
scenario: read($attrs.sourceEditScenario)
};
$scope.fiddle = function(e) {
e.stopPropagation();
openJsFiddle(sources);
}
}
}
function read(text) {
var files = [];
angular.forEach(text ? text.split(' ') : [], function(refId) {
files.push({name: refId.split('-')[0], content: getEmbeddedTemplate(refId)});
});
return files;
}
};
docsApp.directive.docTutorialNav = function(templateMerge) {
var pages = [
'',
'step_00', 'step_01', 'step_02', 'step_03', 'step_04',
'step_05', 'step_06', 'step_07', 'step_08', 'step_09',
'step_10', 'step_11', 'the_end'
];
return {
compile: function(element, attrs) {
var seq = 1 * attrs.docTutorialNav,
props = {
seq: seq,
prev: pages[seq],
next: pages[2 + seq],
diffLo: seq ? (seq - 1): '0~1',
diffHi: seq
};
element.addClass('btn-group');
element.addClass('tutorial-nav');
element.append(templateMerge(
'<li class="btn btn-primary"><a href="tutorial/{{prev}}"><i class="icon-step-backward"></i> Previous</a></li>\n' +
'<li class="btn btn-primary"><a href="http://angular.github.com/angular-phonecat/step-{{seq}}/app"><i class="icon-play"></i> Live Demo</a></li>\n' +
'<li class="btn btn-primary"><a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><i class="icon-search"></i> Code Diff</a></li>\n' +
'<li class="btn btn-primary"><a href="tutorial/{{next}}">Next <i class="icon-step-forward"></i></a></li>', props));
}
};
};
docsApp.directive.docTutorialReset = function() {
function tab(name, command, id, step) {
return '' +
' <div class=\'tab-pane well\' title="' + name + '" value="' + id + '">\n' +
' <ol>\n' +
' <li><p>Reset the workspace to step ' + step + '.</p>' +
' <pre>' + command + '</pre></li>\n' +
' <li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-{{docTutorialReset}}/app">angular\'s server</a>.</p></li>\n' +
' </ol>\n' +
' </div>\n';
}
return {
compile: function(element, attrs) {
var step = attrs.docTutorialReset;
element.html(
'<div ng-hide="show">' +
'<p><a href="" ng-click="show=true;$event.stopPropagation()">Workspace Reset Instructions ➤</a></p>' +
'</div>\n' +
'<div class="tabbable" ng-show="show" ng-model="$cookies.platformPreference">\n' +
tab('Git on Mac/Linux', 'git checkout -f step-' + step, 'gitUnix', step) +
tab('Git on Windows', 'git checkout -f step-' + step, 'gitWin', step) +
tab('Snapshots on Mac/Linux', './goto_step.sh ' + step, 'snapshotUnix', step) +
tab('Snapshots on on Windows', './goto_step.bat ' + step, 'snapshotWin', step) +
'</div>\n');
}
};
}
docsApp.serviceFactory.angularUrls = function($document) {
var urls = {};
angular.forEach($document.find('script'), function(script) {
var match = script.src.match(/^.*\/(angular[^\/]*\.js)$/);
if (match) {
urls[match[1].replace(/(\-\d.*)?(\.min)?\.js$/, '.js')] = match[0];
}
});
return urls;
}
docsApp.serviceFactory.formPostData = function($document) {
return function(url, fields) {
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="_blank"></form>');
angular.forEach(fields, function(value, name) {
var input = angular.element('<input type="hidden" name="' + name + '">');
input.attr('value', value);
form.append(input);
});
$document.find('body').append(form);
form[0].submit();
form.remove();
};
};
docsApp.serviceFactory.openJsFiddle = function(templateMerge, getEmbeddedTemplate, formPostData, angularUrls) {
var HTML = '<div ng-app=\"{{module}}\">\n{{html:2}}</div>',
CSS = '</style> <!-- Ugly Hack due to jsFiddle issue: http://goo.gl/BUfGZ --> \n' +
'{{head:0}}<style>\n.ng-invalid { border: 1px solid red; }\n{{css}}',
SCRIPT = '{{script}}',
SCRIPT_CACHE = '\n\n<!-- {{name}} -->\n<script type="text/ng-template" id="{{name}}">\n{{content:2}}</script>';
return function(content) {
var prop = {
module: content.module,
html: '',
css: '',
script: ''
};
prop.head = templateMerge('<script src="{{url}}"></script>', {url: angularUrls['angular.js']});
angular.forEach(content.html, function(file, index) {
if (index) {
prop.html += templateMerge(SCRIPT_CACHE, file);
} else {
prop.html += file.content;
}
});
angular.forEach(content.js, function(file, index) {
prop.script += file.content;
});
angular.forEach(content.css, function(file, index) {
prop.css += file.content;
});
formPostData("http://jsfiddle.net/api/post/library/pure/", {
title: 'AngularJS Example',
html: templateMerge(HTML, prop),
js: templateMerge(SCRIPT, prop),
css: templateMerge(CSS, prop)
});
};
};
docsApp.serviceFactory.sections = function sections() {
var sections = {
guide: [],
api: [],
tutorial: [],
misc: [],
cookbook: [],
getPage: function(sectionId, partialId) {
var pages = sections[sectionId];
partialId = partialId || 'index';
for (var i = 0, ii = pages.length; i < ii; i++) {
if (pages[i].id == partialId) {
return pages[i];
}
}
return null;
}
};
angular.forEach(NG_PAGES, function(page) {
page.url = page.section + '/' + page.id;
if (page.id == 'angular.Module') {
page.partialUrl = 'partials/api/angular.IModule.html';
} else {
page.partialUrl = 'partials/' + page.url + '.html';
}
sections[page.section].push(page);
});
return sections;
};
docsApp.controller.DocsController = function($scope, $location, $window, $cookies, sections) {
var OFFLINE_COOKIE_NAME = 'ng-offline',
DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/,
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/,
GLOBALS = /^angular\.([^\.]*)$/,
MODULE = /^angular\.module\.([^\.]*)$/,
MODULE_MOCK = /^angular\.mock\.([^\.]*)$/,
MODULE_DIRECTIVE = /^angular\.module\.([^\.]*)(?:\.\$compileProvider)?\.directive\.([^\.]*)$/,
MODULE_DIRECTIVE_INPUT = /^angular\.module\.([^\.]*)\.\$compileProvider\.directive\.input\.([^\.]*)$/,
MODULE_FILTER = /^angular\.module\.([^\.]*)\.\$?filter\.([^\.]*)$/,
MODULE_SERVICE = /^angular\.module\.([^\.]*)\.([^\.]*?)(Provider)?$/,
MODULE_TYPE = /^angular\.module\.([^\.]*)\..*\.([A-Z][^\.]*)$/,
URL = {
module: 'guide/module',
directive: 'guide/directive',
input: 'api/angular.module.ng.$compileProvider.directive.input',
filter: 'guide/dev_guide.templates.filters',
service: 'guide/dev_guide.services',
type: 'guide/types'
};
/**********************************
Publish methods
***********************************/
$scope.navClass = function(page1, page2) {
return {
last: this.$position == 'last',
active: page1 && this.currentPage == page1 || page2 && this.currentPage == page2
};
}
$scope.submitForm = function() {
$scope.bestMatch && $location.path($scope.bestMatch.page.url);
};
$scope.afterPartialLoaded = function() {
var currentPageId = $location.path();
$scope.partialTitle = $scope.currentPage.shortName;
$window._gaq.push(['_trackPageview', currentPageId]);
loadDisqus(currentPageId);
};
/** stores a cookie that is used by apache to decide which manifest ot send */
$scope.enableOffline = function() {
//The cookie will be good for one year!
var date = new Date();
date.setTime(date.getTime()+(365*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
var value = angular.version.full;
document.cookie = OFFLINE_COOKIE_NAME + "="+value+expires+"; path=" + $location.path;
//force the page to reload so server can serve new manifest file
window.location.reload(true);
};
/**********************************
Watches
***********************************/
var SECTION_NAME = {
api: 'API Reference',
guide: 'Developer Guide',
misc: 'Miscellaneous',
tutorial: 'Tutorial',
cookbook: 'Examples'
};
$scope.$watch(function() {return $location.path(); }, function(path) {
// ignore non-doc links which are used in examples
if (DOCS_PATH.test(path)) {
var parts = path.split('/'),
sectionId = parts[1],
partialId = parts[2],
sectionName = SECTION_NAME[sectionId] || sectionId,
page = sections.getPage(sectionId, partialId);
$scope.currentPage = sections.getPage(sectionId, partialId);
if (!$scope.currentPage) {
$scope.partialTitle = 'Error: Page Not Found!';
}
updateSearch();
// Update breadcrumbs
var breadcrumb = $scope.breadcrumb = [],
match;
if (partialId) {
breadcrumb.push({ name: sectionName, url: sectionId });
if (partialId == 'angular.Module') {
breadcrumb.push({ name: 'angular.Module' });
} else if (match = partialId.match(GLOBALS)) {
breadcrumb.push({ name: partialId });
} else if (match = partialId.match(MODULE)) {
breadcrumb.push({ name: match[1] });
} else if (match = partialId.match(MODULE_SERVICE)) {
breadcrumb.push({ name: match[1], url: sectionId + '/angular.module.' + match[1] });
breadcrumb.push({ name: match[2] });
} else if (match = partialId.match(MODULE_FILTER)) {
breadcrumb.push({ name: match[1], url: sectionId + '/angular.module.' + match[1] });
breadcrumb.push({ name: match[2] });
} else if (match = partialId.match(MODULE_DIRECTIVE)) {
breadcrumb.push({ name: match[1], url: sectionId + '/angular.module.' + match[1] });
breadcrumb.push({ name: match[2] });
} else if (match = partialId.match(MODULE_DIRECTIVE_INPUT)) {
breadcrumb.push({ name: match[1], url: sectionId + '/angular.module.' + match[1] });
breadcrumb.push({ name: 'input', url: URL.input });
breadcrumb.push({ name: match[2] });
} else if (match = partialId.match(MODULE_TYPE)) {
breadcrumb.push({ name: match[1], url: sectionId + '/angular.module.' + match[1] });
breadcrumb.push({ name: match[2] });
} else if (match = partialId.match(MODULE_MOCK)) {
breadcrumb.push({ name: 'angular.mock.' + match[1] });
} else {
breadcrumb.push({ name: page.shortName });
}
} else {
breadcrumb.push({ name: sectionName });
}
}
});
$scope.$watch('search', updateSearch);
/**********************************
Initialize
***********************************/
$scope.versionNumber = angular.version.full;
$scope.version = angular.version.full + " " + angular.version.codeName;
$scope.subpage = false;
$scope.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
$scope.futurePartialTitle = null;
$scope.loading = 0;
$scope.URL = URL;
$scope.$cookies = $cookies;
$cookies.platformPreference = $cookies.platformPreference || 'gitUnix';
if (!$location.path() || INDEX_PATH.test($location.path())) {
$location.path('/api').replace();
}
// bind escape to hash reset callback
angular.element(window).bind('keydown', function(e) {
if (e.keyCode === 27) {
$scope.$apply(function() {
$scope.subpage = false;
});
}
});
/**********************************
Private methods
***********************************/
function updateSearch() {
var cache = {},
pages = sections[$location.path().split('/')[1]],
modules = $scope.modules = [],
otherPages = $scope.pages = [],
search = $scope.search,
bestMatch = {page: null, rank:0};
angular.forEach(pages, function(page) {
var match,
id = page.id;
if (!(match = rank(page, search))) return;
if (match.rank > bestMatch.rank) {
bestMatch = match;
}
if (id == 'angular.Module') {
module('ng').types.push(page);
} else if (match = id.match(GLOBALS)) {
module('ng').globals.push(page);
} else if (match = id.match(MODULE)) {
module(match[1]);
} else if (match = id.match(MODULE_SERVICE)) {
module(match[1]).service(match[2])[match[3] ? 'provider' : 'instance'] = page;
} else if (match = id.match(MODULE_FILTER)) {
module(match[1]).filters.push(page);
} else if (match = id.match(MODULE_DIRECTIVE)) {
module(match[1]).directives.push(page);
} else if (match = id.match(MODULE_DIRECTIVE_INPUT)) {
module(match[1]).directives.push(page);
} else if (match = id.match(MODULE_TYPE)) {
module(match[1]).types.push(page);
} else if (match = id.match(MODULE_MOCK)) {
module('ngMock').globals.push(page);
} else if (page.section != 'api' && page.id != 'index'){
otherPages.push(page);
}
});
$scope.bestMatch = bestMatch;
/*************/
function module(name) {
var module = cache[name];
if (!module) {
module = cache[name] = {
name: name,
url: 'api/angular.module.' + name,
globals: [],
directives: [],
services: [],
service: function(name) {
var service = cache[this.name + ':' + name];
if (!service) {
service = {name: name};
cache[this.name + ':' + name] = service;
this.services.push(service);
}
return service;
},
types: [],
filters: []
}
modules.push(module);
}
return module;
}
function rank(page, terms) {
var ranking = {page: page, rank:0},
keywords = page.keywords,
title = page.shortName.toLowerCase();
terms && angular.forEach(terms.toLowerCase().split(' '), function(term) {
var index;
if (ranking) {
if (keywords.indexOf(term) == -1) {
ranking = null;
} else {
ranking.rank ++; // one point for each term found
if ((index = title.indexOf(term)) != -1) {
ranking.rank += 20 - index; // ten points if you match title
}
}
}
});
return ranking;
}
}
function loadDisqus(currentPageId) {
// http://docs.disqus.com/help/2/
window.disqus_shortname = 'angularjs-next';
window.disqus_identifier = currentPageId;
window.disqus_url = 'http://docs.angularjs.org' + currentPageId;
if ($location.host() == 'localhost') {
return; // don't display disqus on localhost, comment this out if needed
//window.disqus_developer = 1;
}
// http://docs.disqus.com/developers/universal/
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://angularjs.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
angular.element(document.getElementById('disqus_thread')).html('');
}
}
angular.module('docsApp', ['ngResource', 'ngCookies', 'ngSanitize', 'bootstrap', 'bootstrapPrettify']).
config(function($locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
}).
factory(docsApp.serviceFactory).
directive(docsApp.directive).
controller(docsApp.controller);
+1
View File
@@ -0,0 +1 @@
../../../../lib/jquery/jquery.js
+1
View File
@@ -0,0 +1 @@
../../../../lib/jquery/jquery.min.js
-52
View File
@@ -1,52 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
;(function()
{
// CommonJS
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
function Brush()
{
var keywords = 'break case catch continue ' +
'default delete do else false ' +
'for function if in instanceof ' +
'new null return super switch ' +
'this throw true try typeof var while with'
;
var r = SyntaxHighlighter.regexLib;
this.regexList = [
{ regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings
{ regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings
{ regex: r.singleLineCComments, css: 'comments' }, // one line comments
{ regex: r.multiLineCComments, css: 'comments' }, // multiline comments
{ regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion
{ regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords
];
this.forHtmlScript(r.scriptScriptTags);
};
Brush.prototype = new SyntaxHighlighter.Highlighter();
Brush.aliases = ['js', 'jscript', 'javascript'];
SyntaxHighlighter.brushes.JScript = Brush;
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();
-69
View File
@@ -1,69 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
;(function()
{
// CommonJS
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
function Brush()
{
function process(match, regexInfo)
{
var constructor = SyntaxHighlighter.Match,
code = match[0],
tag = new XRegExp('(&lt;|<)[\\s\\/\\?]*(?<name>[:\\w-\\.]+)', 'xg').exec(code),
result = []
;
if (match.attributes != null)
{
var attributes,
regex = new XRegExp('(?<name> [\\w:\\-\\.]+)' +
'\\s*=\\s*' +
'(?<value> ".*?"|\'.*?\'|\\w+)',
'xg');
while ((attributes = regex.exec(code)) != null)
{
result.push(new constructor(attributes.name, match.index + attributes.index, 'color1'));
result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string'));
}
}
if (tag != null)
result.push(
new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword')
);
return result;
}
this.regexList = [
{ regex: new XRegExp('(\\&lt;|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\&gt;|>)', 'gm'), css: 'color2' }, // <![ ... [ ... ]]>
{ regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // <!-- ... -->
{ regex: new XRegExp('(&lt;|<)[\\s\\/\\?]*(\\w+)(?<attributes>.*?)[\\s\\/\\?]*(&gt;|>)', 'sg'), func: process }
];
};
Brush.prototype = new SyntaxHighlighter.Highlighter();
Brush.aliases = ['xml', 'xhtml', 'xslt', 'html'];
SyntaxHighlighter.brushes.Xml = Brush;
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();
@@ -1,226 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
.syntaxhighlighter a,
.syntaxhighlighter div,
.syntaxhighlighter code,
.syntaxhighlighter table,
.syntaxhighlighter table td,
.syntaxhighlighter table tr,
.syntaxhighlighter table tbody,
.syntaxhighlighter table thead,
.syntaxhighlighter table caption,
.syntaxhighlighter textarea {
-moz-border-radius: 0 0 0 0 !important;
-webkit-border-radius: 0 0 0 0 !important;
background: none !important;
border: 0 !important;
bottom: auto !important;
float: none !important;
height: auto !important;
left: auto !important;
line-height: 1.1em !important;
margin: 0 !important;
outline: 0 !important;
overflow: visible !important;
padding: 0 !important;
position: static !important;
right: auto !important;
text-align: left !important;
top: auto !important;
vertical-align: baseline !important;
width: auto !important;
box-sizing: content-box !important;
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important;
font-weight: normal !important;
font-style: normal !important;
font-size: 1em !important;
min-height: inherit !important;
min-height: auto !important;
}
.syntaxhighlighter {
width: 100% !important;
margin: 1em 0 1em 0 !important;
position: relative !important;
overflow: auto !important;
font-size: 1em !important;
}
.syntaxhighlighter.source {
overflow: hidden !important;
}
.syntaxhighlighter .bold {
font-weight: bold !important;
}
.syntaxhighlighter .italic {
font-style: italic !important;
}
.syntaxhighlighter .line {
white-space: pre !important;
}
.syntaxhighlighter table {
width: 100% !important;
}
.syntaxhighlighter table caption {
text-align: left !important;
padding: .5em 0 0.5em 1em !important;
}
.syntaxhighlighter table td.code {
width: 100% !important;
}
.syntaxhighlighter table td.code .container {
position: relative !important;
}
.syntaxhighlighter table td.code .container textarea {
box-sizing: border-box !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
height: 100% !important;
border: none !important;
background: white !important;
padding-left: 1em !important;
overflow: hidden !important;
white-space: pre !important;
}
.syntaxhighlighter table td.gutter .line {
text-align: right !important;
padding: 0 0.5em 0 1em !important;
}
.syntaxhighlighter table td.code .line {
padding: 0 1em !important;
}
.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
padding-left: 0em !important;
}
.syntaxhighlighter.show {
display: block !important;
}
.syntaxhighlighter.collapsed table {
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar {
padding: 0.1em 0.8em 0em 0.8em !important;
font-size: 1em !important;
position: static !important;
width: auto !important;
height: auto !important;
}
.syntaxhighlighter.collapsed .toolbar span {
display: inline !important;
margin-right: 1em !important;
}
.syntaxhighlighter.collapsed .toolbar span a {
padding: 0 !important;
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar span a.expandSource {
display: inline !important;
}
.syntaxhighlighter .toolbar {
position: absolute !important;
right: 1px !important;
top: 1px !important;
width: 11px !important;
height: 11px !important;
font-size: 10px !important;
z-index: 10 !important;
}
.syntaxhighlighter .toolbar span.title {
display: inline !important;
}
.syntaxhighlighter .toolbar a {
display: block !important;
text-align: center !important;
text-decoration: none !important;
padding-top: 1px !important;
}
.syntaxhighlighter .toolbar a.expandSource {
display: none !important;
}
.syntaxhighlighter.ie {
font-size: .9em !important;
padding: 1px 0 1px 0 !important;
}
.syntaxhighlighter.ie .toolbar {
line-height: 8px !important;
}
.syntaxhighlighter.ie .toolbar a {
padding-top: 0px !important;
}
.syntaxhighlighter.printing .line.alt1 .content,
.syntaxhighlighter.printing .line.alt2 .content,
.syntaxhighlighter.printing .line.highlighted .number,
.syntaxhighlighter.printing .line.highlighted.alt1 .content,
.syntaxhighlighter.printing .line.highlighted.alt2 .content {
background: none !important;
}
.syntaxhighlighter.printing .line .number {
color: #bbbbbb !important;
}
.syntaxhighlighter.printing .line .content {
color: black !important;
}
.syntaxhighlighter.printing .toolbar {
display: none !important;
}
.syntaxhighlighter.printing a {
text-decoration: none !important;
}
.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
color: black !important;
}
.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
color: #008200 !important;
}
.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
color: blue !important;
}
.syntaxhighlighter.printing .keyword {
color: #006699 !important;
font-weight: bold !important;
}
.syntaxhighlighter.printing .preprocessor {
color: gray !important;
}
.syntaxhighlighter.printing .variable {
color: #aa7700 !important;
}
.syntaxhighlighter.printing .value {
color: #009900 !important;
}
.syntaxhighlighter.printing .functions {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .constants {
color: #0066cc !important;
}
.syntaxhighlighter.printing .script {
font-weight: bold !important;
}
.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
color: gray !important;
}
.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
color: red !important;
}
.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
color: black !important;
}
File diff suppressed because one or more lines are too long
@@ -1,117 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
.syntaxhighlighter {
background-color: white !important;
}
.syntaxhighlighter .line.alt1 {
background-color: white !important;
}
.syntaxhighlighter .line.alt2 {
background-color: white !important;
}
.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
background-color: #e0e0e0 !important;
}
.syntaxhighlighter .line.highlighted.number {
color: black !important;
}
.syntaxhighlighter table caption {
color: black !important;
}
.syntaxhighlighter .gutter {
color: #afafaf !important;
}
.syntaxhighlighter .gutter .line {
border-right: 3px solid #6ce26c !important;
}
.syntaxhighlighter .gutter .line.highlighted {
background-color: #6ce26c !important;
color: white !important;
}
.syntaxhighlighter.printing .line .content {
border: none !important;
}
.syntaxhighlighter.collapsed {
overflow: visible !important;
}
.syntaxhighlighter.collapsed .toolbar {
color: blue !important;
background: white !important;
border: 1px solid #6ce26c !important;
}
.syntaxhighlighter.collapsed .toolbar a {
color: blue !important;
}
.syntaxhighlighter.collapsed .toolbar a:hover {
color: red !important;
}
.syntaxhighlighter .toolbar {
color: white !important;
background: #6ce26c !important;
border: none !important;
}
.syntaxhighlighter .toolbar a {
color: white !important;
}
.syntaxhighlighter .toolbar a:hover {
color: black !important;
}
.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
color: black !important;
}
.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
color: #008200 !important;
}
.syntaxhighlighter .string, .syntaxhighlighter .string a {
color: blue !important;
}
.syntaxhighlighter .keyword {
color: #006699 !important;
}
.syntaxhighlighter .preprocessor {
color: gray !important;
}
.syntaxhighlighter .variable {
color: #aa7700 !important;
}
.syntaxhighlighter .value {
color: #009900 !important;
}
.syntaxhighlighter .functions {
color: #ff1493 !important;
}
.syntaxhighlighter .constants {
color: #0066cc !important;
}
.syntaxhighlighter .script {
font-weight: bold !important;
color: #006699 !important;
background-color: none !important;
}
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
color: gray !important;
}
.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
color: red !important;
}
.syntaxhighlighter .keyword {
font-weight: bold !important;
}
@@ -1,2 +0,0 @@
3.0.83
+11 -18
View File
@@ -7,7 +7,8 @@ var Q = require('qq');
var OUTPUT_DIR = "build/docs/";
var fs = require('fs');
exports.output = function(file, content) {
exports.output = output;
function output(file, content) {
var fullPath = OUTPUT_DIR + file;
var dir = parent(fullPath);
return Q.when(exports.makeDir(dir), function(error) {
@@ -39,7 +40,7 @@ exports.makeDir = function(path) {
};
exports.copyTpl = function(filename) {
return exports.copy('docs/src/templates/' + filename, OUTPUT_DIR + filename);
return exports.copy('docs/src/templates/' + filename, filename);
};
/* Copy files from one place to another.
@@ -56,7 +57,7 @@ exports.copy = function(from, to, transform) {
args.unshift(content.toString());
content = transform.apply(null, args);
}
qfs.write(to, content);
return output(to, content);
});
}
@@ -72,25 +73,15 @@ exports.replace = function(content, replacements) {
}
exports.copyDir = function copyDir(dir) {
return qfs.listDirectoryTree('docs/' + dir).then(function(dirs) {
var done;
dirs.forEach(function(dirToMake) {
done = Q.when(done, function() {
return exports.makeDir("./build/" + dirToMake);
});
});
return done;
}).then(function() {
return qfs.listTree('docs/' + dir);
}).then(function(files) {
files.forEach( function(file) {
exports.copy(file,'./build/' + file);
return qfs.listTree('docs/' + dir).then(function(files) {
files.forEach(function(file) {
exports.copy(file, file.replace(/^docs\//, ''));
});
});
};
exports.merge = function(srcs, to) {
return merge(srcs.map(function(src) { return 'docs/src/templates/' + src; }), OUTPUT_DIR + to);
return merge(srcs.map(function(src) { return 'docs/src/templates/' + src; }), to);
};
function merge(srcs, to) {
@@ -107,7 +98,7 @@ function merge(srcs, to) {
// write to file
return Q.when(done, function(content) {
contents.push(content);
qfs.write(to, contents.join('\n'));
return output(to, contents.join('\n'));
});
}
@@ -130,6 +121,8 @@ exports.toString = function toString(obj) {
obj[key] = toString(value);
});
return obj.join('');
} else if (obj.constructor.name == 'Buffer'){
// do nothing it is Buffer Object
} else {
return JSON.stringify(obj);
}
+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>

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