aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2011-02-03 15:21:34 -0800
committerMisko Hevery2011-02-03 20:03:38 -0800
commit882f412d578e4f01394847fa5fde21b6b4096de2 (patch)
tree2d62877e0c00a5117ae3988172e630b1af9c10e5
parent0d4def68ae0d95dd106d2731d60b6d6b635b5afc (diff)
downloadangular.js-882f412d578e4f01394847fa5fde21b6b4096de2.tar.bz2
Added remainder of the cookbook
-rw-r--r--docs/cookbook.deeplinking.ngdoc88
-rw-r--r--docs/cookbook.form.ngdoc18
-rw-r--r--docs/cookbook.formadvanced.ngdoc39
-rw-r--r--docs/cookbook.helloworld.ngdoc2
-rw-r--r--docs/cookbook.mvc.ngdoc56
-rw-r--r--docs/spec/ngdocSpec.js3
-rw-r--r--docs/src/gen-docs.js3
-rw-r--r--docs/src/ngdoc.js2
-rw-r--r--docs/src/templates/docs.js8
-rw-r--r--docs/src/writer.js10
-rw-r--r--docs/static/settings.html18
-rw-r--r--docs/static/welcome.html5
-rw-r--r--src/Scope.js2
13 files changed, 180 insertions, 74 deletions
diff --git a/docs/cookbook.deeplinking.ngdoc b/docs/cookbook.deeplinking.ngdoc
index a743bf40..90dd50bb 100644
--- a/docs/cookbook.deeplinking.ngdoc
+++ b/docs/cookbook.deeplinking.ngdoc
@@ -3,43 +3,59 @@
@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.
+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.
+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
+# 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
+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.
+* 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
+* {@link ./static/settings.html}
+* {@link ./static/welcome.html}
<doc:example>
<doc:source>
<script>
- angular.service('myApplication', function($route){
+ AppCntl.$inject = ['$route']
+ function AppCntl($route) {
// define routes
- $route.when("", {template:'/cb/welcome.html', controller:WelcomeCntl});
- $route.when("/settings", {template:'/cb/settings.html', controller:SettingsCntl});
+ $route.when("", {template:'./static/welcome.html', controller:WelcomeCntl});
+ $route.when("/settings", {template:'./static/settings.html', controller:SettingsCntl});
+ $route.parent(this);
// initialize the model to something useful
this.person = {
name:'anonymous',
contacts:[{type:'email', url:'anonymous@example.com'}]
};
- }, {inject:['$route']});
+ }
- function WelcomeCntl(){}
+ function WelcomeCntl($route){}
WelcomeCntl.prototype = {
greet: function(){
alert("Hello " + this.person.name);
@@ -60,25 +76,39 @@ http://angularjs.org/cb/welcome.html
}
};
</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 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>
- <small>Your app footer </small>
</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
+# 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.
+* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
+ initialization of the {@link angular.service.$rouet $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/cookbook.form.ngdoc b/docs/cookbook.form.ngdoc
index 4b9709cd..7d55f0a4 100644
--- a/docs/cookbook.form.ngdoc
+++ b/docs/cookbook.form.ngdoc
@@ -66,18 +66,24 @@ allow a user to enter data.
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'))
+ it('should validate zip', function(){
+ expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
.not().toMatch(/ng-validation-error/)
- form.input('user.address.zip').enter('abc');
+ using('.example').input('user.address.zip').enter('abc');
- expect(form.element(':input[name=user.address.zip]').attr('className'))
+ expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
.toMatch(/ng-validation-error/)
});
- iit('should validate state', function(){
+ 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>
diff --git a/docs/cookbook.formadvanced.ngdoc b/docs/cookbook.formadvanced.ngdoc
index 478358db..e3242bdc 100644
--- a/docs/cookbook.formadvanced.ngdoc
+++ b/docs/cookbook.formadvanced.ngdoc
@@ -3,12 +3,15 @@
@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.
+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.state = /^\w\w$/;
+ this.zip = /^\d\d\d\d\d$/;
this.master = {
name: 'John Smith',
address:{
@@ -32,7 +35,6 @@ Here we extend the basic form example to include common features such as reverti
save: function(){
this.master = this.form;
this.cancel();
- alert('SAVED: ' + angular.toJson(this.master));
}
};
</script>
@@ -44,8 +46,8 @@ Here we extend the basic form example to include common features such as reverti
<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/>
+ <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> ]
@@ -69,14 +71,31 @@ Here we extend the basic form example to include common features such as reverti
</div>
</doc:source>
<doc:scenario>
+ it('should enable save button', function(){
+ 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
+#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.
+* 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.helloworld.ngdoc b/docs/cookbook.helloworld.ngdoc
index ba4c6885..ab4c337a 100644
--- a/docs/cookbook.helloworld.ngdoc
+++ b/docs/cookbook.helloworld.ngdoc
@@ -10,7 +10,7 @@
Hello {{name}}!
</doc:source>
<doc:scenario>
- iit('should change the binding when user enters text', function(){
+ it('should change the binding when user enters text', function(){
expect(binding('name')).toEqual('World');
input('name').enter('angular');
expect(binding('name')).toEqual('angular');
diff --git a/docs/cookbook.mvc.ngdoc b/docs/cookbook.mvc.ngdoc
index 2fa2c558..94688547 100644
--- a/docs/cookbook.mvc.ngdoc
+++ b/docs/cookbook.mvc.ngdoc
@@ -3,9 +3,13 @@
@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.
+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.
+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>
@@ -21,7 +25,7 @@ The model is simply the controller's this. This makes it very easy to test the c
'cursor': 'pointer'
};
this.reset();
- this.$watch('$location.hashPath', this.readUrl);
+ this.$watch('$location.hashSearch.board', this.readUrl);
}
TicTacToeCntl.prototype = {
dropPiece: function(row, col) {
@@ -54,16 +58,16 @@ The model is simply the controller's this. This makes it very easy to test the c
},
setUrl: function(){
var rows = [];
- angular.foreach(this.board, function(row){
+ angular.forEach(this.board, function(row){
rows.push(row.join(','));
});
- this.$location.hashPath = rows.join(';') + '/' + this.nextMove;
+ 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){
+ angular.forEach(value[0].split(';'), function(row, i){
this.board[i] = row.split(',');
}, this);
this.grade();
@@ -76,8 +80,8 @@ The model is simply the controller's this. This makes it very easy to test the c
<h3>Tic-Tac-Toe</h3>
<div ng:controller="TicTacToeCntl">
Next Player: {{nextMove}}
- <div ng:show="winner">Player {{winner}} has won!</div>
- <table>
+ <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>
@@ -87,17 +91,35 @@ The model is simply the controller's this. This makes it very easy to test the c
</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
+# 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.
+* 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.
diff --git a/docs/spec/ngdocSpec.js b/docs/spec/ngdocSpec.js
index 69bb5db3..144b3376 100644
--- a/docs/spec/ngdocSpec.js
+++ b/docs/spec/ngdocSpec.js
@@ -296,6 +296,7 @@ describe('ngdoc', function(){
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
'dad{@link angular.foo}\n\n' +
'external{@link http://angularjs.org}\n\n' +
+ 'external{@link ./static.html}\n\n' +
'{@link angular.directive.ng:foo ng:foo}');
doc.parse();
expect(doc.description).
@@ -308,6 +309,8 @@ describe('ngdoc', function(){
toContain('<a href="#!angular.directive.ng:foo"><code>ng:foo</code></a>');
expect(doc.description).
toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
+ expect(doc.description).
+ toContain('<a href="./static.html">./static.html</a>');
});
});
diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js
index 7e17ea8f..83e33942 100644
--- a/docs/src/gen-docs.js
+++ b/docs/src/gen-docs.js
@@ -24,7 +24,8 @@ var writes = callback.chain(function(){
});
var metadata = ngdoc.metadata(docs);
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';'], writes.waitFor());
- writer.copyImages(writes.waitFor());
+ writer.copyDir('img', writes.waitFor());
+ writer.copyDir('static', writes.waitFor());
writer.copy('index.html', writes.waitFor());
writer.copy('docs.js', writes.waitFor());
writer.copy('docs.css', writes.waitFor());
diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js
index 92379420..18ea523b 100644
--- a/docs/src/ngdoc.js
+++ b/docs/src/ngdoc.js
@@ -59,7 +59,7 @@ Doc.prototype = {
markdown: function (text) {
var self = this;
- var IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:)/;
+ var IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/;
var IS_ANGULAR = /^angular\./;
if (!text) return text;
var parts = text.split(/(<pre>[\s\S]*?<\/pre>|<doc:example>[\s\S]*?<\/doc:example>)/),
diff --git a/docs/src/templates/docs.js b/docs/src/templates/docs.js
index e244bc7e..9483496e 100644
--- a/docs/src/templates/docs.js
+++ b/docs/src/templates/docs.js
@@ -1,11 +1,15 @@
+var HAS_HASH = /#/;
DocsController.$inject = ['$location', '$browser', '$window'];
function DocsController($location, $browser, $window) {
this.pages = NG_PAGES;
window.$root = this.$root;
this.$location = $location;
- this.$watch('$location.hashPath', function(hashPath){
- hashPath = hashPath || '!angular';
+ if (!HAS_HASH.test($location.href)) {
+ $location.hashPath = '!angular';
+ }
+
+ this.$watch('$location.hashPath', function(hashPath) {
if (hashPath.match(/^!/)) {
this.partialId = hashPath.substring(1);
this.partialTitle = (angular.Array.filter(NG_PAGES, {id:this.partialId})[0]||{}).name;
diff --git a/docs/src/writer.js b/docs/src/writer.js
index 953302d4..3251b9cd 100644
--- a/docs/src/writer.js
+++ b/docs/src/writer.js
@@ -61,14 +61,12 @@ function copy(from, to, callback) {
});
}
-exports.copyImages = function(callback) {
- exports.makeDir(OUTPUT_DIR + '/img', callback.waitFor(function(){
- fs.readdir('docs/img', callback.waitFor(function(err, files){
+exports.copyDir = function(dir, callback) {
+ exports.makeDir(OUTPUT_DIR + '/' + dir, callback.waitFor(function(){
+ fs.readdir('docs/' + dir, 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());
- }
+ copy('docs/' + dir + '/' + file, OUTPUT_DIR + '/' + dir + '/' + file, callback.waitFor());
});
callback();
}));
diff --git a/docs/static/settings.html b/docs/static/settings.html
new file mode 100644
index 00000000..2fa5dca8
--- /dev/null
+++ b/docs/static/settings.html
@@ -0,0 +1,18 @@
+<label>Name:</label>
+<input type="text" name="form.name" ng:required>
+
+<div ng:repeat="contact in form.contacts">
+ <select name="contact.type">
+ <option>url</option>
+ <option>email</option>
+ <option>phone</option>
+ </select>
+ <input type="text" name="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> \ No newline at end of file
diff --git a/docs/static/welcome.html b/docs/static/welcome.html
new file mode 100644
index 00000000..b085123d
--- /dev/null
+++ b/docs/static/welcome.html
@@ -0,0 +1,5 @@
+Hello {{person.name}},
+<div>
+ Your contact information:
+ <div ng:repeat="contact in person.contacts">{{contact.type}}: {{contact.url|linky}}</div>
+</div>
diff --git a/src/Scope.js b/src/Scope.js
index a73375ab..0084d80c 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -235,7 +235,7 @@ function errorHandlerFor(element, error) {
expect(using('.doc-example-live').repeater('li').row(1)).
toEqual(['1', 'Hello', 'Earth']);
expect(using('.doc-example-live').element('pre').text()).
- toBe('$index=\nsalutation=Hello\nname=Misko');
+ toBe(' $index=\n salutation=Hello\n name=Misko');
});
</doc:scenario>
</doc:example>