diff options
| author | Igor Minar | 2011-06-06 08:50:35 -0700 |
|---|---|---|
| committer | Igor Minar | 2011-06-06 22:52:02 -0700 |
| commit | 7f1e2e48467f80cc083d24b44f088620e4e7bcb6 (patch) | |
| tree | 731a91366c5780985be6d4c5ddbe34e307d5cb70 /docs/content/cookbook | |
| parent | 5533e48dead5cff3107e72ee80bf0f19df77c1e9 (diff) | |
| download | angular.js-7f1e2e48467f80cc083d24b44f088620e4e7bcb6.tar.bz2 | |
new batch of docs
Diffstat (limited to 'docs/content/cookbook')
| -rw-r--r-- | docs/content/cookbook/advancedform.ngdoc | 123 | ||||
| -rw-r--r-- | docs/content/cookbook/buzz.ngdoc | 7 | ||||
| -rw-r--r-- | docs/content/cookbook/deeplinking.ngdoc | 27 | ||||
| -rw-r--r-- | docs/content/cookbook/form.ngdoc | 37 | ||||
| -rw-r--r-- | docs/content/cookbook/helloworld.ngdoc | 13 | ||||
| -rw-r--r-- | docs/content/cookbook/index.ngdoc | 38 | ||||
| -rw-r--r-- | docs/content/cookbook/mvc.ngdoc | 32 |
7 files changed, 240 insertions, 37 deletions
diff --git a/docs/content/cookbook/advancedform.ngdoc b/docs/content/cookbook/advancedform.ngdoc new file mode 100644 index 00000000..e14be8b4 --- /dev/null +++ b/docs/content/cookbook/advancedform.ngdoc @@ -0,0 +1,123 @@ +@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/buzz.ngdoc b/docs/content/cookbook/buzz.ngdoc index 2e82b2d1..52fa5647 100644 --- a/docs/content/cookbook/buzz.ngdoc +++ b/docs/content/cookbook/buzz.ngdoc @@ -3,12 +3,15 @@ @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} +{@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> @@ -40,7 +43,7 @@ to retrieve Buzz activity and comments. <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}} + Expand replies: {{item.links.replies.count}} </a> </h1> {{item.object.content | html}} diff --git a/docs/content/cookbook/deeplinking.ngdoc b/docs/content/cookbook/deeplinking.ngdoc index 7d69ee84..cc748b3f 100644 --- a/docs/content/cookbook/deeplinking.ngdoc +++ b/docs/content/cookbook/deeplinking.ngdoc @@ -3,14 +3,18 @@ @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, @@ -21,23 +25,30 @@ screen will be constructed from an HTML snippet, which we will refer to as the p 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> @@ -48,6 +59,7 @@ The two partials are defined in the following URLs: $route.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl}); $route.parent(this); + // initialize the model to something useful this.person = { name:'anonymous', @@ -55,6 +67,7 @@ The two partials are defined in the following URLs: }; } + function WelcomeCntl($route){} WelcomeCntl.prototype = { greet: function(){ @@ -62,6 +75,7 @@ The two partials are defined in the following URLs: } }; + function SettingsCntl(){ this.cancel(); } @@ -70,6 +84,7 @@ The two partials are defined in the following URLs: this.form = angular.copy(this.person); }, + save: function(){ angular.copy(this.form, this.person); window.location.hash = "#"; @@ -102,13 +117,19 @@ The two partials are defined in the following URLs: + + + # 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 + initialization of the {@link api/angular.service.$route $route} service with the proper URL +routes. +* The {@link api/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 +* The {@link api/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 index 9ccad1c2..6f6447c8 100644 --- a/docs/content/cookbook/form.ngdoc +++ b/docs/content/cookbook/form.ngdoc @@ -3,11 +3,14 @@ @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> @@ -23,14 +26,18 @@ allow a user to enter data. </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/> + <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> ] @@ -49,6 +56,7 @@ allow a user to enter data. <pre>user={{user}}</pre> </div> + </doc:source> <doc:scenario> it('should show debug', function(){ @@ -61,42 +69,53 @@ allow a user to enter data. 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/); + .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/); + .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/); + .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/); + .toMatch(/ng-validation-error/) }); </doc:scenario> </doc:example> + + # Things to notice -* The user data model is initialized {@link angular.directive.@ng:controller controller} and is available in - the {@link angular.scope scope} with the initial data. + +* The user data model is initialized {@link api/angular.directive.@ng:controller controller} and is +available in + the {@link api/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 +* The {@link api/angular.widget.HTML input widgets} simply refer to the model and are auto bound. +* The inputs {@link api/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 diff --git a/docs/content/cookbook/helloworld.ngdoc b/docs/content/cookbook/helloworld.ngdoc index ab4c337a..a2557b5d 100644 --- a/docs/content/cookbook/helloworld.ngdoc +++ b/docs/content/cookbook/helloworld.ngdoc @@ -3,6 +3,7 @@ @name Cookbook: Hello World @description + <doc:example> <doc:source> Your name: <input type="text" name="name" value="World"/> @@ -18,14 +19,18 @@ </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. + +* The script tag that {@link guide/dev_guide.bootstrap bootstraps} the angular environment. +* The text {@link api/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 implicit presence of the `name` variable which is in the root {@link api/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 +* The concept of {@link guide/dev_guide.templates.databinding 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 index 7dc937c5..e8a3d7d7 100644 --- a/docs/content/cookbook/index.ngdoc +++ b/docs/content/cookbook/index.ngdoc @@ -3,58 +3,80 @@ @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 + +{@link 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 + +{@link 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 + +{@link advancedform 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 + +{@link 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 + +{@link 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 + +{@link api/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 + +{@link 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 index 470794b8..bc0eb653 100644 --- a/docs/content/cookbook/mvc.ngdoc +++ b/docs/content/cookbook/mvc.ngdoc @@ -3,19 +3,24 @@ @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(){ + function TicTacToeCntl($location){ + this.$location = $location; this.cellStyle= { 'height': '20px', 'width': '20px', @@ -29,8 +34,8 @@ no connection between the controller and the view. } TicTacToeCntl.prototype = { dropPiece: function(row, col) { - if (!this.winner && !this.board[row][col]) { - this.board[row][col] = this.nextMove; + if (!this.winner && !this.board) { + this.board = this.nextMove; this.nextMove = this.nextMove == 'X' ? 'O' : 'X'; this.setUrl(); } @@ -51,9 +56,9 @@ no connection between the controller and the view. 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 row(r) { return same(b, b, b);} + function col(c) { return same(b, b, b);} + function diagonal(i) { return same(b[1-i], b, b[1+i]);} function same(a, b, c) { return (a==b && b==c) ? a : '';}; }, setUrl: function(){ @@ -63,12 +68,12 @@ no connection between the controller and the view. }); this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove; }, - readUrl: function(value) { + readUrl: function(scope, value) { if (value) { value = value.split('/'); - this.nextMove = value[1]; - angular.forEach(value[0].split(';'), function(row, i){ - this.board[i] = row.split(','); + this.nextMove = value; + angular.forEach(value.split(';'), function(row, i){ + this.board = row.split(','); }, this); this.grade(); } else { @@ -102,6 +107,7 @@ no connection between the controller and the view. expect(element('.winner').text()).toEqual('Player X has won!'); }); + function piece(row, col) { element('.board tr:nth-child('+row+') td:nth-child('+col+')').click(); } @@ -109,8 +115,11 @@ no connection between the controller and the view. </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. @@ -122,4 +131,5 @@ no connection between the controller and 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. + {@link api/angular.scope.$watch $watch()} to set up a listener that invokes `readUrl()` when +needed. |
