Added part of guide documentation and supporting changes to doc generator

This commit is contained in:
Misko Hevery 2011-01-25 21:55:11 -08:00 committed by Igor Minar
parent 8682befc72
commit bd33f60276
18 changed files with 863 additions and 129 deletions

View file

@ -0,0 +1,97 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Bootstrap
@description
# Bootstrap
This section explains how to bootstrap your application to the angular environment using either
the `angular.js` or `angular.min.js` script.
## The bootstrap code
Note that there are two versions of the bootstrap code that you can use:
* `angular-0.0.0.js` - this file is unobfuscated, uncompressed, and thus human-readable.
* `angular-0.0.0.min.js` - this is a compressed and obfuscated version of angular-debug.js.
In this section and throughout the Developer Guide, feel free to use `angular.min.js` instead of
`angular.js` when working through code examples.
## ng:autobind
The simplest way to get an angular application up and running is by inserting a script tag in your
HTML file that bootstraps the `angular.js` code and uses the special `ng:autobind` attribute,
like in this snippet of HTML:
<doc:example>
<doc:source>
Hello {{'World'}}!
</doc:source>
</doc:example>
The `ng:autobind` attribute tells angular to compile and manage the whole HTML document. The
compilation occurs in the page's onLoad handler. Note that you don't need to explicitly add an
onLoad event; auto bind mode takes care of all the magic for you.
## Manual bind
Using autobind mode is a handy way to start using angular, but advanced users who want more
control over the initialization process might prefer to use manual bind mode instead.
The best way to get started with manual bind mode is to look at the magic behind `ng:autobind`
by writing out each step of the autobind process explicitly. Note that the following code is
equivalent to the code in the previous section.
<pre>
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org">
<script type="text/javascript" src="http://code.angularjs.org/angular-0.0.0.min.js"></script>
<script type="text/javascript">
(function(window, previousOnLoad){
window.onload = function(){
try { (previousOnLoad||angular.noop)(); } catch(e) {}
angular.compile(window.document).$init();
};
})(window, window.onload);
</script>
<body>
Hello {{'World'}}!
</body>
</html>
</pre>
This is the sequence that your code should follow if you're writing your own manual binding code:
* After the page is loaded, find the root of the HTML template, which is typically the root of
the document.
* Run the HTML compiler, which converts the templates into an executable, bi-directionally
bound application.
# XML Namespace
**IMPORTANT:** When using angular you must declare the `ng` namespace using the `xmlns` tag.
If you don't declare the namespace, Internet Explorer does not render widgets properly.
<pre>
<html xmlns:ng="http://angularjs.org">
</pre>
# Create your own namespace
If you want to define your own widgets, you must create your own namespace and use that namespace
to form the fully qualified widget name. For example, you could map the alias my to your domain
and create a widget called my:widget. To create your own namespace, simply add another xmlsn tag
to your page, create an alias, and set it to your unique domain:
<pre>
<html xmlns:my="http://mydomain.com">
</pre>
# Global Object
The angular script creates a single global variable `angular` in the global namespace. All APIs are
bound to fields of this global object.

163
docs/guide.compiler.ngdoc Normal file
View file

@ -0,0 +1,163 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Compiler
@description
#Compiler
While angular might look like just a cool way to build web applications, the core of angular is
actually an HTML compiler. The default HTML transformations that this compiler provides are useful
for building generic apps, but you can also use them to create a domain-specific language for
building specific types of web applications.
The compiler allows you to add behavior to existing HTML through widgets, directives, and text
markup.
All of this compilation happens in the web browser, meaning no server is involved.
# The compilation process
This section describes the steps that angular's HTML compiler goes through. If you use
`ng:autobind` in your application, this compilation process happens automatically when the
application is initialized (e.g. when the user loads the app in a browser). If you're an advanced
user using manual bind mode, you can decide when and how often the compilation happens.
First, a bit of background of what the compilation step is for. Every type of
{@link angular.widget widget}, {@link angular.markup markup}, and
{@link angular.directive directive} in angular is defined with a compile function, and that
compile function returns an optional link function. Here is the relationship between the two:
* **compile function** - registers a listener for the widget, markup, or directive's expression.
This function is called exactly once.
* **link function** - sets up the listener. This function can be called multiple times, once per
cloned DOM element (e.g. repeating element).
Note that angular's built-in widgets, markup, and directives have predefined compile and link
functions that you don't need to modify. However, if you're writing your own widgets, markup, or
directives, you write compile and link functions. Refer to the Compiler API for more information.
When the HTML compiler compiles a page, it goes through 3 phases: Compile, Create Root Scope, and
Link.
## 1. Compile Phase
* Recursively traverse the DOM, depth-first.
* Look for a matching compile function of type widget, then markup, then directive.
* If a compile function is found then execute it.
* When the compile function completes, it should return a link function. Aggregate this link
function with all link functions returned previously by step 1c.
* Repeat steps 1c and 1d for all compile functions found. The result of the compilation step is
the aggregate link function, which comprises all of the individual link functions.
## 2. Create Root Scope
* Inject all of the services into the root scope.
## 3. Link Phase
* Execute the aggregate link function with the root scope. The aggregate link function calls all
the individual link functions that were generated in the compile phase.
* If there are any clones of the DOM caused by repeating elements, call the link function multiple
times, one for each repeating item.
Note that while the compile function is executed exactly once, the link function can be executed
multiple times: once for each iteration in a repeater.
# Example
The compilation process is best understood through example. Let's say that in your namespace my,
you want to create a new DOM element <my:greeter/>, which should display a greeting.
If we want this HTML source:
<pre>
<div ng:init="salutation='Hello'; name='World'">
<my:greeter salutation="salutation" name="name"/>
</div>
</pre>
To produce this DOM:
<pre>
<div ng:init="salutation='Hello'; name='World'">
<my:greeter salutation="salutation" name="name"/>
<span class="salutation">Hello</span>
<span class="name">World</span>!
</my:greeter>
</div>
</pre>
Write this widget definition (assuming you've already declared the my namespace in the page):
<pre>
angular.widget('my:greeter', function(compileElement){
var compiler = this;
compileElement.css('display', 'block');
var salutationExp = compileElement.attr('salutation');
var nameExp = compileElement.attr('name');
return function(linkElement){
var salutationSpan = angular.element('<span class="salutation"></span');
var nameSpan = angular.element('<span class="name"></span>');
linkElement.append(salutationSpan);
linkElement.append(compiler.text(' '));
linkElement.append(nameSpan);
linkElement.append(compiler.text('!'));
this.$watch(salutationExp, function(value){
salutationSpan.text(value);
});
this.$watch(nameExp, function(value){
nameSpan.text(value);
});
};
});
</pre>
Note: For more about widgets, see {@link angular.widget Widget}.
## Compilation process for this example
Here are the steps that the compiler goes through for the page that contains this widget definition:
### Compile Phase
* Recursively traverse the DOM depth-first.
* Find the angular.widget definition.
* Find and execute the widget's compileElement function, which includes the following steps:
* Add a style element with attribute display: block; to the template DOM so that the browser
knows to treat the element as block element for rendering. (Note: because this style element
was added on the template compileElement, this style is automatically applied to any clones
of the template (i.e. any repeating elements)).
* Extract the salutation and name HTML attributes as angular expressions.
* Return the aggregate link function, which includes just one link function in this example.
### Link Phase
* Execute the aggregate link function, which includes the following steps:
* Create a <span> element set to the salutation class
* Create a <span> element set to the name class.
* Add the span elements to the linkElement. (Note: be careful not to add them to the
compileElement, because that's the template.)
* Set up watches on the expressions. When an expression changes, copy the data to the
corresponding spans.
## Compiler API
If you define your own widgets, markup, or directives, you need to access the compiler API.
This section describes the methods on the compiler that you can call.
Note: As of 12 August 2010, these methods are subject to change.
Recall that the compile function's this is a reference to the compiler.
* `compile(element)` - returns `linker` - Invoke new instance of compiler to compile a DOM element
and return a linker function. You can apply the linker function to the original element or a
clone of the original element. The linker function returns a scope.
* `comment(commentText)` - returns `element` - Create a comment element.
* `element(elementName)` - returns `element` - Create an element by name.
* `text(text)` - returns `element` - Create a text element.
* `descend([set])` - returns `descend` - State Get or set the current descend state. If true the
compiler will descend to children elements.
* `directives([set])` - returns `directive` - State Get or set the current directives processing
state. The compiler will process directives only when directives set to true.

View file

@ -0,0 +1,41 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Data Binding
@description
# Data Binding
Data-binding allows you to treat the model as the single-source-of-truth of your application, and
consider the view as only a projection of the model, at all times. The process of copying the model
values to the view, and any changes to the view by the user to the model, is known as data-binding.
## Classical Template Systems
<img class="right" src="img/One_Way_Data_Binding.png"/>
At the highest level, angular looks like a just another templating system. But there is one
important reason why angular templating system is different and makes it very good fit for
application development: two-way data binding.
Most templating systems bind data in only one direction: they merge a template and model together
into a view, as illustrated in the diagram to the right. After the merge occurs, any changes to
the model or in related sections of the view are NOT automatically reflected in the view. Worse,
any changes that the user makes to the view are not reflected in the model. This means that the
developer has to write code that constantly syncs the view with the model and the model with the
view.
# angular Template Systems
<img class="right" src="img/Two_Way_Data_Binding.png"/>
The way angular templates works is different, as illustrated in the diagram on the right. They are
different because first the template (which is the uncompiled HTML along with any additional markup
or directives) is compiled on the browser, and second, the compilation step produces a live view.
We say live because any changes to the view are immediately reflected in the model, and any changes
in the model are propagated to the view. This makes the model always the single-source-of-truth for
the application state, greatly simplifying the programing model for the developer. You can think of
the view as simply an instant projection of your model.
Because the view is just a projection of the model, the controller is completely separated from the
view and unaware of it. This makes testing a snap because it is easy to test your controller in
isolation without the view and the related DOM/browser dependency.
For details about how data binding works in angular, see {@link angular.scope Scope}.

36
docs/guide.ngdoc Normal file
View file

@ -0,0 +1,36 @@
@workInProgress
@ngdoc overview
@name Developer Guide
@description
* {@link guide.overview Overview} - An overview of angular, including its philosophy and how it
works.
* {@link guide.bootstrap Bootstrap} - How to bootstrap your application to the angular environment.
* {@link guide.template Template} - How to define your application's view using HTML, CSS, and
other built-in angular constructs.
* {@link guide.compiler Compiler} - All about the HTML compiler that's at the core of angular.
* {@link angular.directive Directive} - How to use XML attributes to augment an existing DOM
element.
* {@link angular.markup Markup} - How to use markup to create shorthand for a widget or a
directive. For example, markup is what allows you to use the double curly brace notation
`{{}}` to bind expressions to elements.
* {@link guide.data-binding Data Binding} - About the mechanism that keeps the model the single
source of truth of your application at all times, with the view as a live projection of the
model.
* {@link angular.filter Filter} - How to format your data for display to the user.
* {@link angular.widget Widget} - How to create new DOM elements that the browser doesn't already
understand.
* {@link angular.validator Validator} - How to validate user input.
* {@link angular.formatter Formatter} - How to format stored data to user-readable text and
parse the text back to the stored form.
* {@link guide.css CSS} - Built-in CSS classes, when angular assigns them, and how to override
their styles.
* {@link angular.scope Scope} - The model in the model-view-controller design pattern. You can
think about scopes as the JavaScript objects that have extra APIs for registering watchers.
* {@link guide.expression Expression} - The bindings that are embedded in an angular View.
* {@link angular.service Service} - Objects that are wired through dependency injection and then
injected into the root scope.
* {@link guide.testing Testing}
* service:$browser(mock)
* {@link guide.downloading Downloading} - How to download, compile, and host the angular
environment on your own server.

166
docs/guide.overview.ngdoc Normal file
View file

@ -0,0 +1,166 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Overview
@description
# What is angular?
Angular teaches your old browser new tricks. It is what HTML would have been if it had been
designed for building web applications.
Take a simple example of user input as shown below. If you were using just HTML and JavaScript to
implement this form, you would need to define listeners, DOM updates, and complex input validators
in order to update and format the result. In angular you can achieve the effect with zero lines
of JavaScript code using a declarative approach. Click on the source tab of the example below to
view the angular implementation of this form.
<doc:example>
<doc:source>
QTY: <input name="qty" value="1" ng:validate="integer:0" ng:required/>
*
Cost: <input name="cost" value="19.95" ng:validate="number" ng:required/>
=
{{qty * cost | currency}}
</doc:source>
<doc:scenario>
it('should show of angular binding', function(){
expect(binding('qty * cost')).toEqual('$19.95');
input('qty').enter('2');
input('cost').enter('5.00');
expect(binding('qty * cost')).toEqual('$10.00');
});
</doc:scenario>
</doc:example>
Angular is to AJAX apps as Ruby on Rails is to round trip apps.
# Angular frees you from:
* **Registering callbacks:** Registering callbacks clutters your code, and it makes it hard to see
the forest from the trees. Removing common boilerplate code such as callbacks is advantageous
because it leaves the JavaScript with a more succinct version of your code, better describing
what your application does.
* **Manipulating HTML DOM programatically:** Manipulating HTML DOM is a cornerstone of AJAX
applications, but it is very cumbersome and error-prone. By declaratively describing how the UI
should change as your application state changes, you are freed from low level DOM manipulation
activities. Most applications written with angular never have to programatically manipulate
the DOM.
* **Marshaling data to and from the UI:** CRUD operations make up the majority of most AJAX
applications. The flow of marshaling data from the server to an internal object to a HTML form,
validating the form, displaying validation errors, returning to an internal model and then back
to the server creates a lot of boilerplate code. angular eliminates almost all of this
boilerplate. leaving code that is richer and describes the overall flow of the application
rather than implementation details.
* **Writing tons of initialization code just to get started:** Typically you need to write a lot
of plumbing and initialization code just to get a basic "Hello World" AJAX app working. With
angular you can bootstrap your app easily using services, which are auto-injected into your
application in a GUICE-like dependency-injection style. This allows you to get started
developing features quickly. As a bonus, you get full control over the initialization process
in automated tests.
# angular is/has:
* **An HTML Compiler:** angular is an HTML compiler in the browser. It allows you to give meaning
to any HTML element, attribute, or text and create new primitives as building blocks for your
application.
* **Declarative:** Declarative means that you describe what the page looks like rather than
instructing how to draw the page. HTML is great at declaring static documents. Angular extends
the declarative nature of HTML beyond static documents to define dynamic applications.
* **Declarative Templates:** In angular, you write HTML to declare the view and UI templates for
your application. You can express many common application constructs without using any
JavaScript at all.
* **Bidirectional Data Binding:** Allows your application to have a single source of truth
(your model), where the HTML is a projection of the internal state of your application, which
you can provide to the user in a declarative way.
* **Built-in Services:** angular provides many standard AJAX operations to get you going quickly,
and dependency-injection allows you to swap them out as needed. Common services include:
Dependency Inject, History Management, URL Router, AJAX/XHR requests, and data caching,
to name a few.
* **Very testable:** Testing difficulty is dramatically affected by the way you structure your
code. With angular, testability is baked in.
# angular is NOT a:
* **Library:** You don't call its functions.
* **Framework:** It does not call your functions.
* **DOM Manipulation Library:** It does not provide a way to manipulate DOM, but does provide
primitives to create UI projections of your data.
* **Widget Library:** There are lots of existing widget libraries that you can integrate with
angular.
# Not just another templating system
At the highest level, angular looks like a just another templating system, but there are few
important reasons why angular is different and makes it a very good fit for application
development.
Angular:
* **Uses HTML/CSS syntax:** This makes it easy to read and can be edited with existing HTML/CSS
authoring tools.
* **Extends HTML vocabulary:** Angular allows you to create new HTML tags, which expand into
dynamic UI components.
* **Executes in the browser:** Removes the round trip to the server for many operations and
creates instant feedback for users.
* **Bidirectional data binding:** The model is the single source of truth. Programmatic changes
to the model are automatically reflected in the view. Any changes by the user to the view are
automatically reflected in the model.
* **Services:** These allow for a reusable way of injecting dependencies into an application.
* **MVC:** Clean separation between model-view-controller, which aids in understanding,
maintenance, and testing of large systems.
# The angular philosophy
Angular is built around the belief that declarative code is preferred over imperative when it
comes to building UIs and connecting the pieces together.
As an example, if you wanted to add a new label to your application, you could do so by simply
adding text to the HTML template, saving the code, and refreshing your browser:
<pre>
<span class="label">Hello</span>
</pre>
In programmatic systems you would have to write and run code like this:
<pre>
var label = new Label();
label.setText('Hello');
label.setClass('label');
parent.addChild(label);
</pre>
## Benefits:
* Compile-free: Change your template and logic code and reload the browser to see it run
immediately. In contrast, programmatic serverside views often need to be compiled and executed
to be viewed, which takes time. This dramatically increases the speed of your development cycle.
* Declarative templates are easier to understand and change than programmatic instructions.
* Declarative templates can be edited in existing HTML editors such as DreamWeaver, Eclipse,
TextMate, Vim, etc.
* Declarative templates can be edited by web designers without the need to work with web
developers.
HTML is missing certain features, which angular adds via its compiler, thereby "teaching" the
browser these new tricks:
* Dynamic behavior
* Componentizing HTML snippets into reusable components
* Dynamically include other HTML templates
* Two-way data binding
* Rich validation in forms
* Model-View-Controller modularization
# Watch a presentation about angular
<object width="480" height="385">
<param name="movie" value="http://www.youtube.com/v/elvcgVSynRg&amp;hl=en_US&amp;fs=1"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
<embed src="http://www.youtube.com/v/elvcgVSynRg&amp;hl=en_US&amp;fs=1"
type="application/x-shockwave-flash" allowscriptaccess="always"
allowfullscreen="true" width="480" height="385"></embed>
</object>
{@link https://docs.google.com/present/edit?id=0Abz6S2TvsDWSZDQ0OWdjaF8yNTRnODczazdmZg&hl=en&authkey=CO-b7oID Presentation}
|
{@link https://docs.google.com/document/edit?id=1ZHVhqC0apbzPRQcgnb1Ye-bAUbNJ-IlFMyPBPCZ2cYU&hl=en&authkey=CInnwLYO Source}

22
docs/guide.template.ngdoc Normal file
View file

@ -0,0 +1,22 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Template
@description
#Template
You can think of a template in angular as a domain-specific language that you can use to easily
build the view of your web application. You create a template by writing HTML and CSS, and you can
add any constructs that you want to the HTML. This means that you can attach rendering and behavior
to any HTML element, attribute or markup text.
In addition to writing HTML and CSS, you can also use the following angular constructs to create
your template:
* **Directive** - XML attributes that augment an existing DOM element.
* **Markup** - Lets you create shorthand for a widget or a directive. For example, markup is what
allows you to use the double curly brace notation {{}} to bind expressions to
elements.
* **Filter** - Lets you format your data for display to the user.
* **Widget** - Lets you create new DOM elements that the browser doesn't already understand.
* **Validator** - Lets you validate user input.
* **Formatter** - Lets you format the input object into a user readable view.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,9 +1,14 @@
var DOM = require('dom.js').DOM;
describe('dom', function(){
var dom;
beforeEach(function(){
dom = new DOM();
});
describe('example', function(){
it('should render code, live, test', function(){
var dom = new DOM();
dom.example('desc', 'src', 'scenario');
expect(dom.toString()).toEqual(
'<h1>Example</h1>\n' +
@ -15,7 +20,6 @@ describe('dom', function(){
});
it('should render non-live, test with description', function(){
var dom = new DOM();
dom.example('desc', 'src', false);
expect(dom.toString()).toEqual('<h1>Example</h1>\n' +
'<div class="example">' +
@ -26,10 +30,32 @@ describe('dom', function(){
});
it('should render non-live, test', function(){
var dom = new DOM();
dom.example('desc', 'src', false);
expect(dom.toString()).toContain('<pre class="brush: js; html-script: true;">src</pre>');
});
});
describe('h', function(){
it('should render using function', function(){
var cbThis;
var cdValue;
dom.h('heading', 'content', function(value){
cbThis = this;
cbValue = value;
});
expect(cbThis).toEqual(dom);
expect(cbValue).toEqual('content');
});
it('should update heading numbers', function(){
dom.h('heading', function(){
this.html('<h1>sub-heading</h1>');
});
expect(dom.toString()).toContain('<h1>heading</h1>');
expect(dom.toString()).toContain('<h2>sub-heading</h2>');
});
});
});

View file

@ -19,9 +19,28 @@ describe('ngdoc', function(){
describe('metadata', function(){
it('should find keywords', function(){
expect(new Doc('\nHello: World! @ignore.').keywords()).toEqual('hello world');
expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world');
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
});
it('should have shortName', function(){
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng:c');
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
});
it('should have depth information', function(){
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].depth).toEqual(2);
expect(ngdoc.metadata([d2])[0].depth).toEqual(2);
expect(ngdoc.metadata([d3])[0].depth).toEqual(1);
});
});
describe('parse', function(){
@ -61,9 +80,68 @@ describe('ngdoc', function(){
expect(doc.example).toEqual('A\n\nB');
});
it('should parse filename', function(){
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
doc.parse(0);
expect(doc.id).toEqual('a.b');
expect(doc.name).toEqual('friendly name');
});
it('should escape <doc:source> element', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:source>\n<>\n</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<doc:source>\n&lt;&gt;\n</doc:source></doc:example><p>after</p>');
});
it('should escape <doc:scenario> element', function(){
var doc = new Doc('@description before <doc:example>' +
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<doc:scenario>\n&lt;&gt;\n</doc:scenario></doc:example><p>after</p>');
});
describe('sorting', function(){
function property(name) {
return function(obj) {return obj[name];};
}
function noop(){}
function doc(type, name){
return {
id: name,
ngdoc: type,
keywords: noop
};
}
var angular_widget = doc('overview', 'angular.widget');
var angular_x = doc('function', 'angular.x');
var angular_y = doc('property', 'angular.y');
it('should put angular.fn() in front of angular.widget, etc', function(){
expect(ngdoc.metadata([angular_widget, angular_y, angular_x]).map(property('id')))
.toEqual(['angular.x', 'angular.y', 'angular.widget' ]);
});
});
});
});
describe('scenario', function(){
it('should render from @example/@scenario and <doc:example>', function(){
var doc = new Doc(
'@id id\n' +
'@description <doc:example><doc:scenario>scenario0</doc:scenario></doc:example>' +
'@example exempleText\n' +
'@scenario scenario1\n' +
'@scenario scenario2').parse();
expect(ngdoc.scenarios([doc])).toContain('describe("id"');
expect(ngdoc.scenarios([doc])).toContain('navigateTo("index.html#!id")');
expect(ngdoc.scenarios([doc])).toContain('\n scenario0\n');
expect(ngdoc.scenarios([doc])).toContain('\n scenario1\n');
expect(ngdoc.scenarios([doc])).toContain('\n scenario2\n');
});
});
describe('markdown', function(){
@ -86,8 +164,9 @@ describe('ngdoc', function(){
it('should replace text between two <pre></pre> tags', function() {
expect(markdown('<pre>x</pre># One<pre>b</pre>')).
toMatch('</div><h3>One</h3><div');
toMatch('</div><h1>One</h1><div');
});
});
describe('trim', function(){
@ -230,7 +309,7 @@ describe('ngdoc', function(){
expect(doc.description).
toBe('<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
'<h3>bah</h3>\n\n' +
'<h1>bah</h1>\n\n' +
'<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
@ -243,18 +322,15 @@ describe('ngdoc', function(){
'{@link angular.directive.ng:foo ng:foo}');
doc.parse();
expect(doc.description).
toBe('<p>foo <a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
'<p>da <a href="#!angular.foo"><code>bar foo bar</code></a> </p>\n\n' +
'<p>dad<a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
'<p><a href="#!angular.directive.ng:foo"><code>ng:foo</code></a></p>');
toContain('foo <a href="#!angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
toContain('da <a href="#!angular.foo"><code>bar foo bar</code></a>');
expect(doc.description).
toContain('dad<a href="#!angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
toContain('<a href="#!angular.directive.ng:foo"><code>ng:foo</code></a>');
});
it('should increment all headings by two', function() {
var doc = new Doc('@description # foo\nabc\n## bar \n xyz');
doc.parse();
expect(doc.description).
toBe('<h3>foo</h3>\n\n<p>abc</p>\n\n<h4>bar</h4>\n\n<p>xyz</p>');
});
});
describe('@example', function(){
@ -291,8 +367,8 @@ describe('ngdoc', function(){
});
describe('@deprecated', function() {
it('should parse @deprecated', function() {
describe('@depricated', function() {
it('should parse @depricated', function() {
var doc = new Doc('@deprecated Replaced with foo.');
doc.parse();
expect(doc.deprecated).toBe('Replaced with foo.');
@ -315,6 +391,17 @@ describe('ngdoc', function(){
});
describe('usage', function(){
describe('overview', function(){
it('should supress description heading', function(){
var doc = new Doc('@ngdoc overview\n@name angular\n@description\n#heading\ntext');
doc.parse();
expect(doc.html()).toContain('text');
expect(doc.html()).toContain('<h2>heading</h2>');
expect(doc.html()).not.toContain('Description');
});
});
describe('filter', function(){
it('should format', function(){
var doc = new Doc({

View file

@ -3,12 +3,18 @@
*/
exports.DOM = DOM;
exports.htmlEscape = htmlEscape;
//////////////////////////////////////////////////////////
function htmlEscape(text){
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function DOM(){
this.out = [];
this.headingDepth = 1;
this.headingDepth = 0;
}
var INLINE_TAGS = {
@ -23,7 +29,7 @@ DOM.prototype = {
text: function(content) {
if (typeof content == "string") {
this.out.push(content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'));
this.out.push(htmlEscape(content));
} else if (typeof content == 'function') {
content.call(this, this);
} else if (content instanceof Array) {
@ -33,6 +39,13 @@ DOM.prototype = {
html: function(html) {
if (html) {
var headingDepth = this.headingDepth;
for ( var i = 10; i > 0; --i) {
html = html
.replace(new RegExp('(<\/?h)' + i + '(>)', 'gm'), function(all, start, end){
return start + (i + headingDepth) + end;
});
}
this.out.push(html);
}
},
@ -80,15 +93,17 @@ DOM.prototype = {
h: function(heading, content, fn){
if (content==undefined || content && content.legth == 0) return;
this.tag('h' + this.headingDepth, heading);
this.headingDepth++;
this.tag('h' + this.headingDepth, heading);
var className = typeof heading == 'string'
? {'class': heading.toLowerCase().replace(/[^\d\w_]/, '-')}
? {'class': heading.toLowerCase().replace(/[^\d\w_]/mg, '-').replace(/-+/gm, '-')}
: null;
if (content instanceof Array) {
this.ul(content, className, fn);
} else if (fn) {
this.tag('div', className, fn);
this.tag('div', className, function(){
fn.call(this, content);
});
} else {
this.tag('div', className, content);
}

View file

@ -20,10 +20,11 @@ var work = callback.chain(function(){
var writes = callback.chain(function(){
ngdoc.merge(docs);
docs.forEach(function(doc){
writer.output(doc.name + '.html', doc.html(), writes.waitFor());
writer.output(doc.id + '.html', doc.html(), writes.waitFor());
});
var metadata = ngdoc.metadata(docs);
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata), ';'], writes.waitFor());
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';'], writes.waitFor());
writer.copyImages(writes.waitFor());
writer.copy('index.html', writes.waitFor());
writer.copy('docs.js', writes.waitFor());
writer.copy('docs.css', writes.waitFor());

View file

@ -4,6 +4,7 @@
var Showdown = require('showdown').Showdown;
var DOM = require('dom.js').DOM;
var htmlEscape = require('dom.js').htmlEscape;
var NEW_LINE = /\n\r?/;
exports.markdown = markdown;
@ -39,7 +40,7 @@ Doc.prototype = {
var words = [];
var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
tokens.forEach(function(key){
var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/);
var match = key.match(/^(([\$\_a-z]|ng\:)[\w\_\-]{2,})/);
if (match){
key = match[1];
if (!keywords[key]) {
@ -57,6 +58,11 @@ Doc.prototype = {
var atText;
var match;
var self = this;
this.scenarios = [];
this.requires = [];
this.param = [];
this.properties = [];
this.methods = [];
self.text.split(NEW_LINE).forEach(function(line){
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
// we found @name ...
@ -73,6 +79,9 @@ Doc.prototype = {
});
flush();
this.shortName = (this.name || '').split(/[\.#]/).pop();
this.id = this.id // if we have an id just use it
|| (((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // try to extract it from file name
|| this.name; // default to name
this.description = markdown(this.description);
this['this'] = markdown(this['this']);
this.exampleDescription = markdown(this.exampleDescription || this.exampleDesc);
@ -94,7 +103,6 @@ Doc.prototype = {
optional: !!match[2],
'default':match[6]
};
self.param = self.param || [];
self.param.push(param);
} else if (atName == 'returns') {
var match = text.match(/^{([^}=]+)}\s+(.*)/);
@ -105,9 +113,16 @@ Doc.prototype = {
type: match[1],
description: markdown(text.replace(match[0], match[2]))
};
} else if(atName == 'description') {
text.replace(/<doc:scenario>([\s\S]*)<\/doc:scenario>/mi,
function(_, scenario){
self.scenarios.push(scenario);
});
self.description = text;
} else if(atName == 'requires') {
self.requires = self.requires || [];
self.requires.push(text);
} else if(atName == 'scenario') {
self.scenarios.push(text);
} else if(atName == 'property') {
var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
if (!match) {
@ -118,7 +133,6 @@ Doc.prototype = {
name: match[3],
description: match[5] || ''
};
self.properties = self.properties || [];
self.properties.push(property);
} else {
self[atName] = text;
@ -135,25 +149,10 @@ Doc.prototype = {
notice('workInProgress', 'Work in Progress',
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
notice('deprecated', 'Deprecated API', self.deprecated);
dom.h('Description', self.description, html);
dom.h('Dependencies', self.requires);
usage();
dom.h('Methods', self.methods, function(method){
var signature = (method.param || []).map(property('name'));
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description);
method.html_usage_parameters(dom);
dom.example(method.exampleDescription, method.example, false);
});
});
dom.h('Properties', self.properties, function(property){
dom.h(property.name, function(){
dom.text(property.description);
dom.example(property.exampleDescription, property.example, false);
});
});
(self['html_usage_' + self.ngdoc] || function(){
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom);
dom.example(self.exampleDescription, self.example, self.scenario);
});
@ -162,29 +161,6 @@ Doc.prototype = {
//////////////////////////
function html(text){
this.html(text);
}
function usage(){
(self['html_usage_' + self.ngdoc] || function(){
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom);
}
function section(name, property, fn) {
var value = self[property];
if (value) {
dom.h2(name);
if (typeof value == 'string') {
value = markdown(value) + '\n';
fn ? fn(value) : dom.html(value);
} else if (value instanceof Array) {
dom.ul(value, fn);
}
}
}
function notice(name, legend, msg){
if (self[name] == undefined) return;
dom.tag('fieldset', {'class':name}, function(dom){
@ -240,6 +216,8 @@ Doc.prototype = {
html_usage_function: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.code(function(){
dom.text(self.name);
@ -256,11 +234,13 @@ Doc.prototype = {
html_usage_directive: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
dom.text('<' + self.element + ' ');
dom.text(self.shortName);
if (self.param) {
if (self.param.length) {
dom.text('="' + self.param[0].name + '"');
}
dom.text('>\n ...\n');
@ -272,6 +252,8 @@ Doc.prototype = {
html_usage_filter: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.tag('code', function(){
@ -302,6 +284,8 @@ Doc.prototype = {
html_usage_formatter: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
@ -340,6 +324,8 @@ Doc.prototype = {
html_usage_validator: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
@ -368,6 +354,8 @@ Doc.prototype = {
html_usage_widget: function(dom){
var self = this;
dom.h('Description', self.description, dom.html);
dom.h('Dependencies', self.requires);
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
@ -376,7 +364,7 @@ Doc.prototype = {
dom.text(self.element);
dom.text(' ');
dom.text(self.shortName.substring(1));
if (self.param) {
if (self.param.length) {
dom.text('="');
dom.text(self.param[0].name);
dom.text('"');
@ -407,9 +395,27 @@ Doc.prototype = {
},
html_usage_overview: function(dom){
dom.html(this.description);
},
html_usage_service: function(dom){
dom.h('Description', this.description, dom.html);
dom.h('Dependencies', this.requires);
dom.h('Methods', this.methods, function(method){
var signature = (method.param || []).map(property('name'));
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description);
method.html_usage_parameters(dom);
dom.example(method.exampleDescription, method.example, false);
});
});
dom.h('Properties', this.properties, function(property){
dom.h(property.name, function(){
dom.text(property.description);
dom.example(property.exampleDescription, property.example, false);
});
});
},
parameters: function(dom, separator, skipFirst, prefix) {
@ -433,7 +439,7 @@ Doc.prototype = {
//////////////////////////////////////////////////////////
function markdown (text) {
if (!text) return text;
var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/),
var parts = text.split(/(<pre>[\s\S]*?<\/pre>|<doc:example>[\s\S]*?<\/doc:example>)/),
match;
parts.forEach(function(text, i){
@ -443,39 +449,51 @@ function markdown (text) {
content.replace(/</g, '&lt;').replace(/>/g, '&gt;') +
'</pre></div>';
});
} else if (text.match(/^<doc:example>/)) {
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
function(_, before, content, after){
return before + htmlEscape(content) + after;
});
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
function(_, before, content, after){
return before + htmlEscape(content) + after;
});
} else {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###'));
while (match = text.match(R_LINK)) {
text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' +
(match[4] || match[1]) +
'</code></a>');
}
text = text.replace(/{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/gm,
function(_all, url, _2, _3, title){
return '<a href="#!' + url + '">'
+ (url.match(/^angular\./) ? '<code>' : '')
+ (title || url)
+ (url.match(/^angular\./) ? '</code>' : '')
+ '</a>';
});
text = new Showdown.converter().makeHtml(text);
}
parts[i] = text;
});
return parts.join('');
};
var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m;
// 1 123 3 4 42
//////////////////////////////////////////////////////////
function scenarios(docs){
var specs = [];
docs.forEach(function(doc){
specs.push('describe("' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
specs.push(' browser().navigateTo("index.html#!' + doc.id + '");');
specs.push(' });');
specs.push('');
doc.scenarios.forEach(function(scenario){
specs.push(trim(scenario, ' '));
specs.push('');
});
specs.push('});');
specs.push('');
if (doc.scenario) {
specs.push('describe("');
specs.push(doc.name);
specs.push('", function(){\n');
specs.push(' beforeEach(function(){\n');
specs.push(' browser().navigateTo("index.html#!' + doc.name + '");');
specs.push(' });\n\n');
specs.push(doc.scenario);
specs.push('\n});\n\n');
}
});
return specs;
return specs.join('\n');
}
@ -483,8 +501,17 @@ function scenarios(docs){
function metadata(docs){
var words = [];
docs.forEach(function(doc){
var path = (doc.name || '').split(/(\.|\:\s+)/);
for ( var i = 1; i < path.length; i++) {
path.splice(i, 1);
}
var depth = path.length - 1;
var shortName = path.pop();
words.push({
name:doc.name,
id: doc.id,
name: doc.name,
depth: depth,
shortName: shortName,
type: doc.ngdoc,
keywords:doc.keywords()
});
@ -493,37 +520,50 @@ function metadata(docs){
return words;
}
function keywordSort(a,b){
// supper ugly comparator that orders all utility methods and objects before all the other stuff
// like widgets, directives, services, etc.
// Mother of all beautiful code please forgive me for the sin that this code certainly is.
if (a.name === b.name) return 0;
if (a.name === 'angular') return -1;
if (b.name === 'angular') return 1;
function namespacedName(page) {
return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
var KEYWORD_PRIORITY = {
'.guide': 1,
'.guide.overview': 1,
'.angular': 7,
'.angular.Array': 7,
'.angular.Object': 7,
'.angular.directive': 7,
'.angular.filter': 7,
'.angular.formatter': 7,
'.angular.scope': 7,
'.angular.service': 7,
'.angular.validator': 7,
'.angular.widget': 7
};
function keywordSort(a, b){
function mangleName(doc) {
var path = doc.id.split(/\./);
var mangled = [];
var partialName = '';
path.forEach(function(name){
partialName += '.' + name;
mangled.push(KEYWORD_PRIORITY[partialName] || 5);
mangled.push(name);
});
return mangled.join('.');
}
var namespacedA = namespacedName(a),
namespacedB = namespacedName(b);
return namespacedA < namespacedB ? -1 : 1;
var nameA = mangleName(a);
var nameB = mangleName(b);
return nameA < nameB ? -1 : (nameA > nameB ? 1 : 0);
}
//////////////////////////////////////////////////////////
function trim(text) {
function trim(text, prefix) {
var MAX = 9999;
var empty = RegExp.prototype.test.bind(/^\s*$/);
var lines = text.split('\n');
var minIndent = MAX;
prefix = prefix || '';
lines.forEach(function(line){
minIndent = Math.min(minIndent, indent(line));
});
for ( var i = 0; i < lines.length; i++) {
lines[i] = lines[i].substring(minIndent);
lines[i] = prefix + lines[i].substring(minIndent);
}
// remove leading lines
while (empty(lines[0])) {

View file

@ -58,11 +58,22 @@
function indent(text) {
var lines = text.split(/\n/);
var lineNo = [];
// remove any leading blank lines
while (lines[0].match(/^\s*$/)) lines.shift();
// remove any trailing blank lines
while (lines[lines.length - 1].match(/^\s*$/)) lines.pop();
var minIndent = 999;
for ( var i = 0; i < lines.length; i++) {
lines[i] = ' ' + lines[i];
lineNo.push(6 + i);
var line = lines[0];
var indent = line.match(/^\s*/)[0];
if (indent !== line && indent.length < minIndent) {
minIndent = indent.length;
}
}
for ( var i = 0; i < lines.length; i++) {
lines[i] = ' ' + lines[i].substring(minIndent);
lineNo.push(5 + i);
}
return {html: lines.join('\n'), hilite: lineNo.join(',') };
};

View file

@ -181,11 +181,16 @@ a {
}
#sidebar ul li.level-0 {
margin-top: 0.5em;
margin-left: 0em;
font-weight: bold;
font-size: 1.2em;
}
#sidebar ul li.level-0:first-child {
margin-top: 0;
}
#sidebar ul li.level-1.level-angular {
font-family: monospace;
font-weight: normal;
@ -211,6 +216,11 @@ a {
font-family: monospace;
}
#sidebar ul li.level-4 {
margin-left: 4em;
font-family: monospace;
}
/* Warning and Info Banners */
@ -282,3 +292,11 @@ a {
#main::-webkit-scrollbar {
background-color:#fff;
}
/* Content */
img.right {
float: right;
}
h1, h2, h3, h4, h5 {
clear: both;
}

View file

@ -4,7 +4,7 @@ function DocsController($location, $browser, $window) {
window.$root = this.$root;
this.getUrl = function(page){
return '#!' + page.name;
return '#!' + page.id;
};
this.getCurrentPartial = function(){
@ -13,14 +13,14 @@ function DocsController($location, $browser, $window) {
this.getTitle = function(){
var hashPath = $location.hashPath || '!angular';
if (hashPath.match(/^!angular/)) {
if (hashPath.match(/^!/)) {
this.partialTitle = hashPath.substring(1);
}
return this.partialTitle;
};
this.getClass = function(page) {
var depth = page.name.split(/\./).length - 1,
var depth = page.depth,
cssClass = 'level-' + depth + (page.name == this.getTitle() ? ' selected' : '');
if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular';
@ -40,8 +40,4 @@ function DocsController($location, $browser, $window) {
}
angular.filter('short', function(name){
return (name||'').split(/\./).pop();
});
SyntaxHighlighter['defaults'].toolbar = false;

View file

@ -34,7 +34,7 @@
tabindex="1" accesskey="s"/>
<ul id="api-list">
<li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)">
<a href="{{getUrl(page)}}" ng:click="" tabindex="2">{{page.name | short}}</a>
<a href="{{getUrl(page)}}" ng:click="" tabindex="2">{{page.shortName}}</a>
</li>
</ul>
</div>

View file

@ -50,12 +50,27 @@ exports.makeDir = function (path, callback) {
};
exports.copy = function(filename, callback){
//console.log('writing', OUTPUT_DIR + filename, '...');
fs.readFile('docs/src/templates/' + filename, function(err, content){
if (err) return callback.error(err);
fs.writeFile(
OUTPUT_DIR + filename,
content,
callback);
});
copy('docs/src/templates/' + filename, OUTPUT_DIR + filename, callback);
};
function copy(from, to, callback) {
//console.log('writing', to, '...');
fs.readFile(from, function(err, content){
if (err) return callback.error(err);
fs.writeFile(to, content, callback);
});
}
exports.copyImages = function(callback) {
exports.makeDir(OUTPUT_DIR + '/img', callback.waitFor(function(){
fs.readdir('docs/img', callback.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
if (file.match(/\.(png|gif|jpg|jpeg)$/)) {
copy('docs/img/' + file, OUTPUT_DIR + '/img/' + file, callback.waitFor());
}
});
callback();
}));
}));
};