aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/cookbook.buzz.ngdoc61
-rw-r--r--docs/cookbook.deeplinking.ngdoc80
-rw-r--r--docs/cookbook.form.ngdoc18
-rw-r--r--docs/cookbook.formadvanced.ngdoc78
-rw-r--r--docs/cookbook.mvc.ngdoc99
-rw-r--r--src/services.js7
6 files changed, 333 insertions, 10 deletions
diff --git a/docs/cookbook.buzz.ngdoc b/docs/cookbook.buzz.ngdoc
index 0e89cd20..2e82b2d1 100644
--- a/docs/cookbook.buzz.ngdoc
+++ b/docs/cookbook.buzz.ngdoc
@@ -1,4 +1,63 @@
@workInProgress
@ngdoc overview
-@name Cookbook: Resources - Buzz Demo
+@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/cookbook.deeplinking.ngdoc b/docs/cookbook.deeplinking.ngdoc
index 1629a0b1..a743bf40 100644
--- a/docs/cookbook.deeplinking.ngdoc
+++ b/docs/cookbook.deeplinking.ngdoc
@@ -2,3 +2,83 @@
@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.
+
+[edit] 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.
+[edit] 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:
+
+http://angularjs.org/cb/settings.html
+http://angularjs.org/cb/welcome.html
+
+
+<doc:example>
+ <doc:source>
+ <script>
+ angular.service('myApplication', function($route){
+ // define routes
+ $route.when("", {template:'/cb/welcome.html', controller:WelcomeCntl});
+ $route.when("/settings", {template:'/cb/settings.html', controller:SettingsCntl});
+
+ // initialize the model to something useful
+ this.person = {
+ name:'anonymous',
+ contacts:[{type:'email', url:'anonymous@example.com'}]
+ };
+ }, {inject:['$route']});
+
+ function WelcomeCntl(){}
+ 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>
+ <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>
+ <div style="border: 1px solid blue; margin: 0;">
+ <ng:include src="$route.current.template" scope="$route.current.scope"></ng:include>
+ </div>
+ <small>Your app footer </small>
+ </doc:source>
+ <doc:scenario>
+ </doc:scenario>
+</doc:example>
+
+
+
+Things to notice
+
+Routes are defined in the myApplication service. The service is initialized on application startup. Initialization of the services causes the initialization of the $route service with the proper URL routes. The $route service then watches the URL and instantiates the appropriate controller when the URL changes.
+The ng:include widget loads the partial when the URL changes. It also sets the partial scope to the newly instantiated controller.
+Changing the URL is sufficient to change the controller and screen/view/partial. It makes no difference whether the URL is changed programatically or by the user.
diff --git a/docs/cookbook.form.ngdoc b/docs/cookbook.form.ngdoc
index 80ec7b98..4b9709cd 100644
--- a/docs/cookbook.form.ngdoc
+++ b/docs/cookbook.form.ngdoc
@@ -3,7 +3,9 @@
@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.
+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>
@@ -52,17 +54,27 @@ A web application's main purpose is to present and gather data. For this reason
it('should show debug', function(){
expect(binding('user')).toMatch(/John Smith/);
});
- iit('should add contact', function(){
+ 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/);
});
- iit('should remove contact', function(){
+ it('should remove contact', function(){
+ using('.example').element('a:contains(X)').click();
+ expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
});
iit('should validate zip', function(){
+ var form = using('.example');
+ expect(form.element(':input[name=user.address.zip]').attr('className'))
+ .not().toMatch(/ng-validation-error/)
+
+ form.input('user.address.zip').enter('abc');
+
+ expect(form.element(':input[name=user.address.zip]').attr('className'))
+ .toMatch(/ng-validation-error/)
});
iit('should validate state', function(){
diff --git a/docs/cookbook.formadvanced.ngdoc b/docs/cookbook.formadvanced.ngdoc
index 5b93b33e..478358db 100644
--- a/docs/cookbook.formadvanced.ngdoc
+++ b/docs/cookbook.formadvanced.ngdoc
@@ -2,3 +2,81 @@
@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>
+ function UserForm(){
+ 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();
+ alert('SAVED: ' + angular.toJson(this.master));
+ }
+ };
+ </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:/^\w\w$/"/>
+ <input type="text" name="form.address.zip" size="5" ng:required ng:validate="regexp:/^\d\d\d\d\d$/"/><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>
+ </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/cookbook.mvc.ngdoc b/docs/cookbook.mvc.ngdoc
index d63c1f25..2fa2c558 100644
--- a/docs/cookbook.mvc.ngdoc
+++ b/docs/cookbook.mvc.ngdoc
@@ -2,3 +2,102 @@
@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.hashPath', 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.hashPath = 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 ng:show="winner">Player {{winner}} has won!</div>
+ <table>
+ <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>
+ </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 $watch() to set up a listener that invokes readUrl() when needed.
diff --git a/src/services.js b/src/services.js
index f54234a6..b7e7c02f 100644
--- a/src/services.js
+++ b/src/services.js
@@ -22,19 +22,14 @@ function angularServiceInject(name, fn, inject, eager) {
* suffer from window globality.
*
* @example
-<<<<<<< HEAD
- <input ng:init="$window = $service('$window'); greeting='Hello World!'" type="text" name="greeting" />
- <button ng:click="$window.alert(greeting)">ALERT</button>
-=======
<doc:example>
<doc:source>
- <input ng:init="greeting='Hello World!'" type="text" name="greeting" />
+ <input ng:init="$window = $service('$window'); greeting='Hello World!'" type="text" name="greeting" />
<button ng:click="$window.alert(greeting)">ALERT</button>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
->>>>>>> changed the documentation @example to use <doc:example>
*/
angularServiceInject("$window", bind(window, identity, window), [], EAGER);