aboutsummaryrefslogtreecommitdiffstats
path: root/docs/cookbook.mvc.ngdoc
diff options
context:
space:
mode:
Diffstat (limited to 'docs/cookbook.mvc.ngdoc')
-rw-r--r--docs/cookbook.mvc.ngdoc99
1 files changed, 99 insertions, 0 deletions
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.