aboutsummaryrefslogtreecommitdiffstats
path: root/docs/content/cookbook
diff options
context:
space:
mode:
authorMisko Hevery2011-04-29 15:18:27 -0700
committerIgor Minar2011-06-06 22:28:38 -0700
commit11e9572b952e49b01035e956c412d6095533031a (patch)
tree04dbf96802f552693d44c541c0d825a2769e3d57 /docs/content/cookbook
parentb6bc6c2ddf1ae1523ec7e4cb92db209cd6501181 (diff)
downloadangular.js-11e9572b952e49b01035e956c412d6095533031a.tar.bz2
Move documentation under individual headings
Diffstat (limited to 'docs/content/cookbook')
-rw-r--r--docs/content/cookbook/buzz.ngdoc63
-rw-r--r--docs/content/cookbook/deeplinking.ngdoc114
-rw-r--r--docs/content/cookbook/form.ngdoc103
-rw-r--r--docs/content/cookbook/formadvanced.ngdoc105
-rw-r--r--docs/content/cookbook/helloworld.ngdoc31
-rw-r--r--docs/content/cookbook/index.ngdoc60
-rw-r--r--docs/content/cookbook/mvc.ngdoc125
7 files changed, 601 insertions, 0 deletions
diff --git a/docs/content/cookbook/buzz.ngdoc b/docs/content/cookbook/buzz.ngdoc
new file mode 100644
index 00000000..2e82b2d1
--- /dev/null
+++ b/docs/content/cookbook/buzz.ngdoc
@@ -0,0 +1,63 @@
+@workInProgress
+@ngdoc overview
+@name Cookbook: Resources - Buzz
+@description
+
+External resources are URLs that provide JSON data, which are then rendered with the help of
+templates. angular has a resource factory that can be used to give names to the URLs and then
+attach behavior to them. For example you can use the
+{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz API}
+to retrieve Buzz activity and comments.
+
+<doc:example>
+ <doc:source>
+ <script>
+ BuzzController.$inject = ['$resource'];
+ function BuzzController($resource){
+ this.Activity = $resource(
+ 'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
+ {alt:'json', callback:'JSON_CALLBACK'},
+ { get: {method:'JSON', params:{visibility:'@self'}},
+ replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}
+ });
+ }
+ BuzzController.prototype = {
+ fetch: function(){
+ this.activities = this.Activity.get({userId:this.userId});
+ },
+ expandReplies: function(activity) {
+ activity.replies =
+ this.Activity.replies({userId:this.userId, activityId:activity.id});
+ }
+ };
+ </script>
+ <div ng:controller="BuzzController">
+ <input name="userId" value="googlebuzz"/>
+ <button ng:click="fetch()">fetch</button>
+ <hr/>
+ <div class="buzz" ng:repeat="item in activities.data.items">
+ <h1 style="font-size: 15px;">
+ <img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
+ <a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
+ <a href="" ng:click="expandReplies(item)" style="float: right;">
+ Expand replies: {{item.links.replies[0].count}}
+ </a>
+ </h1>
+ {{item.object.content | html}}
+ <div class="reply" ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
+ <img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
+ <a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
+ {{reply.content | html}}
+ </div>
+ </div>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('fetch buzz and expand', function(){
+ element(':button:contains(fetch)').click();
+ expect(repeater('div.buzz').count()).toBeGreaterThan(0);
+ element('.buzz a:contains(Expand replies):first').click();
+ expect(repeater('div.reply').count()).toBeGreaterThan(0);
+ });
+ </doc:scenario>
+</doc:example>
diff --git a/docs/content/cookbook/deeplinking.ngdoc b/docs/content/cookbook/deeplinking.ngdoc
new file mode 100644
index 00000000..7d69ee84
--- /dev/null
+++ b/docs/content/cookbook/deeplinking.ngdoc
@@ -0,0 +1,114 @@
+@workInProgress
+@ngdoc overview
+@name Cookbook: Deep Linking
+@description
+
+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
+which make the common case described here very easy to implement.
+
+# Assumptions
+
+Your application consists of a single HTML page which bootstraps the application. We will refer
+to this page as the chrome.
+Your application is divided into several screens (or views) which the user can visit. For example,
+the home screen, settings screen, details screen, etc. For each of these screens, we would like to
+assign a URL so that it can be bookmarked and later restored. Each of these screens will be
+associated with a controller which define the screen's behavior. The most common case is that the
+screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
+have multiple partials, but a single partial is the most common construct. This example makes the
+partial boundary visible using a blue line.
+
+You can make a routing table which shows which URL maps to which partial view template and which
+controller.
+
+# Example
+
+In this example we have a simple app which consist of two screens:
+
+* Welcome: url `#` Show the user contact information.
+* Settings: url `#/settings` Show an edit screen for user contact information.
+
+
+The two partials are defined in the following URLs:
+
+* {@link ./examples/settings.html}
+* {@link ./examples/welcome.html}
+
+
+<doc:example>
+ <doc:source>
+ <script>
+ AppCntl.$inject = ['$route']
+ function AppCntl($route) {
+ // define routes
+ $route.when("", {template:'./examples/welcome.html', controller:WelcomeCntl});
+ $route.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
+ $route.parent(this);
+
+ // initialize the model to something useful
+ this.person = {
+ name:'anonymous',
+ contacts:[{type:'email', url:'anonymous@example.com'}]
+ };
+ }
+
+ function WelcomeCntl($route){}
+ WelcomeCntl.prototype = {
+ greet: function(){
+ alert("Hello " + this.person.name);
+ }
+ };
+
+ function SettingsCntl(){
+ this.cancel();
+ }
+ SettingsCntl.prototype = {
+ cancel: function(){
+ this.form = angular.copy(this.person);
+ },
+
+ save: function(){
+ angular.copy(this.form, this.person);
+ window.location.hash = "#";
+ }
+ };
+ </script>
+ <div ng:controller="AppCntl">
+ <h1>Your App Chrome</h1>
+ [ <a href="#">Welcome</a> | <a href="#/settings">Settings</a> ]
+ <hr/>
+ <span style="background-color: blue; color: white; padding: 3px;">
+ Partial: {{$route.current.template}}
+ </span>
+ <ng:view style="border: 1px solid blue; margin: 0; display:block; padding:1em;"></ng:view>
+ <small>Your app footer </small>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ 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/);
+ });
+ </doc:scenario>
+</doc:example>
+
+
+
+# Things to notice
+
+* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
+ initialization of the {@link angular.service.$route $route} service with the proper URL routes.
+* The {@link angular.service.$route $route} service then watches the URL and instantiates the
+ appropriate controller when the URL changes.
+* The {@link angular.widget.ng:view ng:view} 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.
diff --git a/docs/content/cookbook/form.ngdoc b/docs/content/cookbook/form.ngdoc
new file mode 100644
index 00000000..c9fd9e9a
--- /dev/null
+++ b/docs/content/cookbook/form.ngdoc
@@ -0,0 +1,103 @@
+@workInProgress
+@ngdoc overview
+@name Cookbook: Form
+@description
+
+A web application's main purpose is to present and gather data. For this reason angular strives
+to make both of these operations trivial. This example shows off how you can build a simple form to
+allow a user to enter data.
+
+
+<doc:example>
+ <doc:source>
+ <script>
+ function FormController(){
+ this.user = {
+ name: 'John Smith',
+ address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
+ contacts:[{type:'phone', value:'1(234) 555-1212'}]
+ };
+ this.state = /^\w\w$/;
+ this.zip = /^\d\d\d\d\d$/;
+ }
+ </script>
+ <div ng:controller="FormController" class="example">
+
+ <label>Name:</label><br/>
+ <input type="text" name="user.name" ng:required/> <br/><br/>
+
+ <label>Address:</label><br/>
+ <input type="text" name="user.address.line1" size="33" ng:required/> <br/>
+ <input type="text" name="user.address.city" size="12" ng:required/>,
+ <input type="text" name="user.address.state" size="2" ng:required ng:validate="regexp:state"/>
+ <input type="text" name="user.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
+
+ <label>Phone:</label>
+ [ <a href="" ng:click="user.contacts.$add()">add</a> ]
+ <div ng:repeat="contact in user.contacts">
+ <select name="contact.type">
+ <option>email</option>
+ <option>phone</option>
+ <option>pager</option>
+ <option>IM</option>
+ </select>
+ <input type="text" name="contact.value" ng:required/>
+ [ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]
+ </div>
+ <hr/>
+ Debug View:
+ <pre>user={{user}}</pre>
+ </div>
+
+ </doc:source>
+ <doc:scenario>
+ it('should show debug', function(){
+ expect(binding('user')).toMatch(/John Smith/);
+ });
+ it('should add contact', function(){
+ using('.example').element('a:contains(add)').click();
+ using('.example div:last').input('contact.value').enter('you@example.org');
+ expect(binding('user')).toMatch(/\(234\) 555\-1212/);
+ expect(binding('user')).toMatch(/you@example.org/);
+ });
+
+ it('should remove contact', function(){
+ using('.example').element('a:contains(X)').click();
+ expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
+ });
+
+ it('should validate zip', function(){
+ expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
+ .not().toMatch(/ng-validation-error/);
+
+ using('.example').input('user.address.zip').enter('abc');
+
+ expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
+ .toMatch(/ng-validation-error/);
+ });
+
+ it('should validate state', function(){
+ expect(using('.example').element(':input[name=user.address.state]').attr('className'))
+ .not().toMatch(/ng-validation-error/);
+
+ using('.example').input('user.address.state').enter('XXX');
+
+ expect(using('.example').element(':input[name=user.address.state]').attr('className'))
+ .toMatch(/ng-validation-error/);
+ });
+ </doc:scenario>
+</doc:example>
+
+
+# Things to notice
+
+* The user data model is initialized {@link angular.ng:controller controller} and is available in
+ the {@link angular.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.
+* The {@link angular.widget.HTML input widgets} simply refer to the model and are auto bound.
+* The inputs {@link angular.validator validate}. (Try leaving them blank or entering non digits
+ in the zip field)
+* In your application you can simply read from or write to the model and the form will be updated.
+* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
+ reflected in the view.
diff --git a/docs/content/cookbook/formadvanced.ngdoc b/docs/content/cookbook/formadvanced.ngdoc
new file mode 100644
index 00000000..181dd5e9
--- /dev/null
+++ b/docs/content/cookbook/formadvanced.ngdoc
@@ -0,0 +1,105 @@
+@workInProgress
+@ngdoc overview
+@name Cookbook: Advanced Form
+@description
+
+Here we extend the basic form example to include common features such as reverting, dirty state
+detection, and preventing invalid form submission.
+
+<doc:example>
+ <doc:source>
+ <script>
+ UserForm.$inject = ['$invalidWidgets'];
+ function UserForm($invalidWidgets){
+ this.$invalidWidgets = $invalidWidgets;
+ this.state = /^\w\w$/;
+ this.zip = /^\d\d\d\d\d$/;
+ this.master = {
+ name: 'John Smith',
+ address:{
+ line1: '123 Main St.',
+ city:'Anytown',
+ state:'AA',
+ zip:'12345'
+ },
+ contacts:[
+ {type:'phone', value:'1(234) 555-1212'}
+ ]
+ };
+ this.cancel();
+ }
+
+ UserForm.prototype = {
+ cancel: function(){
+ this.form = angular.copy(this.master);
+ },
+
+ save: function(){
+ this.master = this.form;
+ this.cancel();
+ }
+ };
+ </script>
+ <div ng:controller="UserForm">
+
+ <label>Name:</label><br/>
+ <input type="text" name="form.name" ng:required/> <br/><br/>
+
+ <label>Address:</label><br/>
+ <input type="text" name="form.address.line1" size="33" ng:required/> <br/>
+ <input type="text" name="form.address.city" size="12" ng:required/>,
+ <input type="text" name="form.address.state" size="2" ng:required ng:validate="regexp:state"/>
+ <input type="text" name="form.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
+
+ <label>Phone:</label>
+ [ <a href="" ng:click="form.contacts.$add()">add</a> ]
+ <div ng:repeat="contact in form.contacts">
+ <select name="contact.type">
+ <option>email</option>
+ <option>phone</option>
+ <option>pager</option>
+ <option>IM</option>
+ </select>
+ <input type="text" name="contact.value" ng:required/>
+ [ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
+ </div>
+ <button ng:click="cancel()" disabled="{{master.$equals(form)}}">Cancel</button>
+ <button ng:click="save()" disabled="{{$invalidWidgets.visible() || master.$equals(form)}}">Save</button>
+
+ <hr/>
+ Debug View:
+ <pre>form={{form}}
+ master={{master}}</pre>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should enable save button', function(){
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('');
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('change');
+ expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
+ element(':button:contains(Save)').click();
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ });
+ it('should enable cancel button', function(){
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('change');
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
+ element(':button:contains(Cancel)').click();
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
+ expect(element(':input[name=form.name]').val()).toEqual('John Smith');
+ });
+ </doc:scenario>
+</doc:example>
+
+
+#Things to notice
+
+* Cancel & save buttons are only enabled if the form is dirty -- there is something to cancel or
+ save.
+* Save button is only enabled if there are no validation errors on the form.
+* Cancel reverts the form changes back to original state.
+* Save updates the internal model of the form.
+* Debug view shows the two models. One presented to the user form and the other being the pristine
+ copy master.
diff --git a/docs/content/cookbook/helloworld.ngdoc b/docs/content/cookbook/helloworld.ngdoc
new file mode 100644
index 00000000..ab4c337a
--- /dev/null
+++ b/docs/content/cookbook/helloworld.ngdoc
@@ -0,0 +1,31 @@
+@workInProgress
+@ngdoc overview
+@name Cookbook: Hello World
+@description
+
+<doc:example>
+ <doc:source>
+ Your name: <input type="text" name="name" value="World"/>
+ <hr/>
+ Hello {{name}}!
+ </doc:source>
+ <doc:scenario>
+ it('should change the binding when user enters text', function(){
+ expect(binding('name')).toEqual('World');
+ input('name').enter('angular');
+ expect(binding('name')).toEqual('angular');
+ });
+ </doc:scenario>
+</doc:example>
+
+# Things to notice
+
+Take a look through the source and note:
+
+* The script tag that {@link guide.bootstrap bootstraps} the angular environment.
+* The text {@link angular.widget.HTML input widget} which is bound to the greeting name text.
+* No need for listener registration and event firing on change events.
+* The implicit presence of the `name` variable which is in the root {@link angular.scope scope}.
+* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
+* The concept of {@link guide.data-binding data binding}, which reflects any changes to the
+ input field in the greeting text.
diff --git a/docs/content/cookbook/index.ngdoc b/docs/content/cookbook/index.ngdoc
new file mode 100644
index 00000000..7dc937c5
--- /dev/null
+++ b/docs/content/cookbook/index.ngdoc
@@ -0,0 +1,60 @@
+@workInProgress
+@ngdoc overview
+@name Cookbook
+@description
+
+Welcome to the angular cookbook. Here we will show you typical uses of angular by example.
+
+
+# Hello World
+
+{@link cookbook.helloworld Hello World}: The simplest possible application that demonstrates the
+classic Hello World!
+
+
+# Basic Form
+
+{@link cookbook.form Basic Form}: Displaying forms to the user for editing is the bread and butter
+of web applications. Angular makes forms easy through bidirectional data binding.
+
+
+# Advanced Form
+
+{@link cookbook.formadvanced Advanced Form}: Taking the form example to the next level and
+providing advanced features such as dirty detection, form reverting and submit disabling if
+validation errors exist.
+
+
+# Model View Controller
+
+{@link cookbook.mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
+to separate the behavior (JavaScript controller) from the presentation (HTML view). This
+separation aids in maintainability and testability of your project.
+
+
+# Multi-page App and Deep Linking
+
+{@link cookbook.deeplinking Deep Linking}: An AJAX application never navigates away from the
+first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
+is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
+prevent you from emailing links to a particular screen within your application.
+
+Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
+allowing you to send links to specific screens in your app.
+
+
+# Services
+
+{@link angular.service Services}: Services are long lived objects in your applications that are
+available across controllers. A collection of useful services are pre-bundled with angular but you
+will likely add your own. Services are initialized using dependency injection, which resolves the
+order of initialization. This safeguards you from the perils of global state (a common way to
+implement long lived objects).
+
+
+# External Resources
+
+{@link cookbook.buzz Resources}: Web applications must be able to communicate with the external
+services to get and update data. Resources are the abstractions of external URLs which are
+specially tailored to angular data binding.
+
diff --git a/docs/content/cookbook/mvc.ngdoc b/docs/content/cookbook/mvc.ngdoc
new file mode 100644
index 00000000..94688547
--- /dev/null
+++ b/docs/content/cookbook/mvc.ngdoc
@@ -0,0 +1,125 @@
+@workInProgress
+@ngdoc overview
+@name Cookbook: MVC
+@description
+
+MVC allows for a clean an testable separation between the behavior (controller) and the view
+(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
+view. This makes it very easy for the controller and the view to share the model.
+
+The model is simply the controller's this. This makes it very easy to test the controller in
+isolation since one can simply instantiate the controller and test without a view, because there is
+no connection between the controller and the view.
+
+
+<doc:example>
+ <doc:source>
+ <script>
+ function TicTacToeCntl(){
+ this.cellStyle= {
+ 'height': '20px',
+ 'width': '20px',
+ 'border': '1px solid black',
+ 'text-align': 'center',
+ 'vertical-align': 'middle',
+ 'cursor': 'pointer'
+ };
+ this.reset();
+ this.$watch('$location.hashSearch.board', this.readUrl);
+ }
+ TicTacToeCntl.prototype = {
+ dropPiece: function(row, col) {
+ if (!this.winner && !this.board[row][col]) {
+ this.board[row][col] = this.nextMove;
+ this.nextMove = this.nextMove == 'X' ? 'O' : 'X';
+ this.setUrl();
+ }
+ },
+ reset: function(){
+ this.board = [
+ ['', '', ''],
+ ['', '', ''],
+ ['', '', '']
+ ];
+ this.nextMove = 'X';
+ this.winner = '';
+ this.setUrl();
+ },
+ grade: function(){
+ var b = this.board;
+ this.winner =
+ row(0) || row(1) || row(2) ||
+ col(0) || col(1) || col(2) ||
+ diagonal(-1) || diagonal(1);
+ function row(r) { return same(b[r][0], b[r][1], b[r][2]);}
+ function col(c) { return same(b[0][c], b[1][c], b[2][c]);}
+ function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
+ function same(a, b, c) { return (a==b && b==c) ? a : '';};
+ },
+ setUrl: function(){
+ var rows = [];
+ angular.forEach(this.board, function(row){
+ rows.push(row.join(','));
+ });
+ this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove;
+ },
+ readUrl: function(value) {
+ if (value) {
+ value = value.split('/');
+ this.nextMove = value[1];
+ angular.forEach(value[0].split(';'), function(row, i){
+ this.board[i] = row.split(',');
+ }, this);
+ this.grade();
+ } else {
+ this.reset();
+ }
+ }
+ };
+ </script>
+ <h3>Tic-Tac-Toe</h3>
+ <div ng:controller="TicTacToeCntl">
+ Next Player: {{nextMove}}
+ <div class="winner" ng:show="winner">Player {{winner}} has won!</div>
+ <table class="board">
+ <tr ng:repeat="row in board" style="height:15px;">
+ <td ng:repeat="cell in row" ng:style="cellStyle"
+ ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
+ </tr>
+ </table>
+ <button ng:click="reset()">reset board</button>
+ </div>
+ </doc:source>
+ <doc:scenario>
+ it('should play a game', function(){
+ piece(1, 1);
+ expect(binding('nextMove')).toEqual('O');
+ piece(3, 1);
+ expect(binding('nextMove')).toEqual('X');
+ piece(1, 2);
+ piece(3, 2);
+ piece(1, 3);
+ expect(element('.winner').text()).toEqual('Player X has won!');
+ });
+
+ function piece(row, col) {
+ element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
+ }
+ </doc:scenario>
+</doc:example>
+
+
+# Things to notice
+
+* The controller is defined in JavaScript and has no reference to the rendering logic.
+* The controller is instantiated by <angular/> and injected into the view.
+* The controller can be instantiated in isolation (without a view) and the code will still execute.
+ This makes it very testable.
+* The HTML view is a projection of the model. In the above example, the model is stored in the
+ board variable.
+* All of the controller's properties (such as board and nextMove) are available to the view.
+* Changing the model changes the view.
+* The view can call any controller function.
+* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
+ hash so the browser's back button will undo game steps. See deep-linking. This example calls
+ {@link angular.Scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.