Update everything

This commit is contained in:
Colin Frei 2013-04-07 10:12:25 +02:00
parent bf368181a4
commit 72a485d6e8
319 changed files with 67958 additions and 13948 deletions

View file

@ -0,0 +1,139 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>Here we extend the basic form example to include common features such as reverting, dirty state
detection, and preventing invalid form submission.</p>
<h3>Source</h3>
<div source-edit="" source-edit-deps="angular.js script.js" source-edit-html="index.html" source-edit-css="" source-edit-js="script.js" source-edit-unit="" source-edit-scenario="scenario.js"></div>
<div class="tabbable"><div class="tab-pane" title="index.html">
<pre class="prettyprint linenums" ng-set-text="index.html" ng-html-wrap=" angular.js script.js"></pre>
<script type="text/ng-template" id="index.html">
<div ng-controller="UserForm">
<form name="myForm">
<label>Name:</label><br/>
<input type="text" ng-model="form.name" required/> <br/><br/>
<label>Address:</label> <br/>
<input type="text" ng-model="form.address.line1" size="33" required/> <br/>
<input type="text" ng-model="form.address.city" size="12" required/>,
<input type="text" ng-model="form.address.state" size="2"
ng-pattern="state" required/>
<input type="text" ng-model="form.address.zip" size="5"
ng-pattern="zip" required/><br/><br/>
<label>Contacts:</label>
[ <a href="" ng-click="addContact()">add</a> ]
<div ng-repeat="contact in form.contacts">
<select ng-model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng-model="contact.value" required/>
[ <a href="" ng-click="removeContact(contact)">X</a> ]
</div>
<button ng-click="cancel()" ng-disabled="isCancelDisabled()">Cancel</button>
<button ng-click="save()" ng-disabled="isSaveDisabled()">Save</button>
</form>
<hr/>
Debug View:
<pre>form={{form}}</pre>
</div>
</script>
</div>
<div class="tab-pane" title="script.js">
<pre class="prettyprint linenums" ng-set-text="script.js"></pre>
<script type="text/ng-template" id="script.js">
function UserForm($scope) {
var master = {
name: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
},
contacts:[
{type:'phone', value:'1(234) 555-1212'}
]
};
$scope.state = /^\w\w$/;
$scope.zip = /^\d\d\d\d\d$/;
$scope.cancel = function() {
$scope.form = angular.copy(master);
};
$scope.save = function() {
master = $scope.form;
$scope.cancel();
};
$scope.addContact = function() {
$scope.form.contacts.push({type:'', value:''});
};
$scope.removeContact = function(contact) {
var contacts = $scope.form.contacts;
for (var i = 0, ii = contacts.length; i < ii; i++) {
if (contact === contacts[i]) {
contacts.splice(i, 1);
}
}
};
$scope.isCancelDisabled = function() {
return angular.equals(master, $scope.form);
};
$scope.isSaveDisabled = function() {
return $scope.myForm.$invalid || angular.equals(master, $scope.form);
};
$scope.cancel();
}
</script>
</div>
<div class="tab-pane" title="End to end test">
<pre class="prettyprint linenums" ng-set-text="scenario.js"></pre>
<script type="text/ng-template" id="scenario.js">
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[ng\\:model="form.name"]').val()).toEqual('John Smith');
});
</script>
</div>
</div><h3>Demo</h3>
<div class="well doc-example-live" ng-embed-app="" ng-set-html="index.html" ng-eval-javascript="script.js"></div>
<h2>Things to notice</h2>
<ul>
<li>Cancel &amp; save buttons are only enabled if the form is dirty — there is something to cancel or
save.</li>
<li>Save button is only enabled if there are no validation errors on the form.</li>
<li>Cancel reverts the form changes back to original state.</li>
<li>Save updates the internal model of the form.</li>
<li>Debug view shows the two models. One presented to the user form and the other being the pristine
copy master.</li>
</ul></div>

View file

@ -0,0 +1,73 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>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
<a href="http://code.google.com/apis/buzz/v1/getting_started.html#background-operations|">Google Buzz API</a>
to retrieve Buzz activity and comments.</p>
<h3>Source</h3>
<div source-edit="" source-edit-deps="angular.js script.js" source-edit-html="index.html-1" source-edit-css="" source-edit-js="script.js-0" source-edit-unit="" source-edit-scenario="scenario.js-2"></div>
<div class="tabbable"><div class="tab-pane" title="index.html">
<pre class="prettyprint linenums" ng-set-text="index.html-1" ng-html-wrap=" angular.js script.js"></pre>
<script type="text/ng-template" id="index.html-1">
<div ng-controller="BuzzController">
<input ng-model="userId"/>
<button ng-click="fetch()">fetch</button>
<hr/>
<div class="buzz" ng-repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img ng-src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng-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>
</h2>
{{item.object.content | html}}
<div class="reply" ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img ng-src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng-href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
{{reply.content | html}}
</div>
</div>
</div>
</script>
</div>
<div class="tab-pane" title="script.js">
<pre class="prettyprint linenums" ng-set-text="script.js-0"></pre>
<script type="text/ng-template" id="script.js-0">
BuzzController.$inject = ['$scope', '$resource'];
function BuzzController($scope, $resource) {
$scope.userId = 'googlebuzz';
$scope.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt: 'json', callback: 'JSON_CALLBACK'},
{ get: {method: 'JSONP', params: {visibility: '@self'}},
replies: {method: 'JSONP', params: {visibility: '@self', comments: '@comments'}}
});
$scope.fetch = function() {
$scope.activities = $scope.Activity.get({userId:this.userId});
}
$scope.expandReplies = function(activity) {
activity.replies = $scope.Activity.replies({userId: this.userId, activityId: activity.id});
}
};
</script>
</div>
<div class="tab-pane" title="End to end test">
<pre class="prettyprint linenums" ng-set-text="scenario.js-2"></pre>
<script type="text/ng-template" id="scenario.js-2">
xit('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);
});
</script>
</div>
</div><h3>Demo</h3>
<div class="well doc-example-live" ng-embed-app="" ng-set-html="index.html-1" ng-eval-javascript="script.js-0"></div></div>

View file

@ -0,0 +1,172 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>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.</p>
<p>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.</p>
<h2>Assumptions</h2>
<p>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.</p>
<p>You can make a routing table which shows which URL maps to which partial view template and which
controller.</p>
<h2>Example</h2>
<p>In this example we have a simple app which consist of two screens:</p>
<ul>
<li>Welcome: url <code>welcome</code> Show the user contact information.</li>
<li>Settings: url <code>settings</code> Show an edit screen for user contact information.</li>
</ul>
<h3>Source</h3>
<div source-edit="deepLinking" source-edit-deps="angular.js angular-sanitize.js script.js" source-edit-html="index.html-4 settings.html welcome.html" source-edit-css="style.css" source-edit-js="script.js-3" source-edit-unit="" source-edit-scenario="scenario.js-5"></div>
<div class="tabbable"><div class="tab-pane" title="index.html">
<pre class="prettyprint linenums" ng-set-text="index.html-4" ng-html-wrap="deepLinking angular.js angular-sanitize.js script.js"></pre>
<script type="text/ng-template" id="index.html-4">
<div ng-controller="AppCntl">
<h2>Your App Chrome</h2>
[ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
<hr/>
<span class="partial-info">
Partial: {{$route.current.template}}
</span>
<div ng-view></div>
<small>Your app footer </small>
</div>
</script>
</div>
<div class="tab-pane" title="settings.html">
<pre class="prettyprint linenums" ng-set-text="settings.html"></pre>
<script type="text/ng-template" id="settings.html">
<label>Name:</label>
<input type="text" ng:model="form.name" required>
<div ng:repeat="contact in form.contacts">
<select ng:model="contact.type">
<option>url</option>
<option>email</option>
<option>phone</option>
</select>
<input type="text" ng:model="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>
</script>
</div>
<div class="tab-pane" title="welcome.html">
<pre class="prettyprint linenums" ng-set-text="welcome.html"></pre>
<script type="text/ng-template" id="welcome.html">
Hello {{person.name}},
<div>
Your contact information:
<div ng:repeat="contact in person.contacts">{{contact.type}}:
<span ng-bind-html="contact.url|linky"></span>
</div>
</div>
</script>
</div>
<div class="tab-pane" title="style.css">
<pre class="prettyprint linenums" ng-set-text="style.css"></pre>
<style type="text/css" id="style.css">
[ng-view] {
border: 1px solid blue;
margin: 0;
padding:1em;
}
.partial-info {
background-color: blue;
color: white;
padding: 3px;
}
</style>
</div>
<div class="tab-pane" title="script.js">
<pre class="prettyprint linenums" ng-set-text="script.js-3"></pre>
<script type="text/ng-template" id="script.js-3">
angular.module('deepLinking', ['ngSanitize'])
.config(function($routeProvider) {
$routeProvider.
when("/welcome", {templateUrl:'welcome.html', controller:WelcomeCntl}).
when("/settings", {templateUrl:'settings.html', controller:SettingsCntl});
});
AppCntl.$inject = ['$scope', '$route']
function AppCntl($scope, $route) {
$scope.$route = $route;
// initialize the model to something useful
$scope.person = {
name:'anonymous',
contacts:[{type:'email', url:'anonymous@example.com'}]
};
}
function WelcomeCntl($scope) {
$scope.greet = function() {
alert("Hello " + $scope.person.name);
};
}
function SettingsCntl($scope, $location) {
$scope.cancel = function() {
$scope.form = angular.copy($scope.person);
};
$scope.save = function() {
angular.copy($scope.form, $scope.person);
$location.path('/welcome');
};
$scope.cancel();
}
</script>
</div>
<div class="tab-pane" title="End to end test">
<pre class="prettyprint linenums" ng-set-text="scenario.js-5"></pre>
<script type="text/ng-template" id="scenario.js-5">
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/);
});
</script>
</div>
</div><h3>Demo</h3>
<div class="well doc-example-live" ng-embed-app="deepLinking" ng-set-html="index.html-4" ng-eval-javascript="script.js-3"></div>
<h2>Things to notice</h2>
<ul>
<li>Routes are defined in the <code>AppCntl</code> class. The initialization of the controller causes the
initialization of the <a href="api/ng.$route"><code>$route</code></a> service with the proper URL
routes.</li>
<li>The <a href="api/ng.$route"><code>$route</code></a> service then watches the URL and instantiates the
appropriate controller when the URL changes.</li>
<li>The <a href="api/ng.directive:ngView"><code>ngView</code></a> widget loads the
view when the URL changes. It also sets the view scope to the newly instantiated controller.</li>
<li>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.</li>
</ul></div>

View file

@ -0,0 +1,125 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>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.</p>
<h3>Source</h3>
<div source-edit="" source-edit-deps="angular.js script.js" source-edit-html="index.html-7" source-edit-css="" source-edit-js="script.js-6" source-edit-unit="" source-edit-scenario="scenario.js-8"></div>
<div class="tabbable"><div class="tab-pane" title="index.html">
<pre class="prettyprint linenums" ng-set-text="index.html-7" ng-html-wrap=" angular.js script.js"></pre>
<script type="text/ng-template" id="index.html-7">
<div ng-controller="FormController" class="example">
<label>Name:</label><br>
<input type="text" ng-model="user.name" required/> <br><br>
<label>Address:</label><br>
<input type="text" ng-model="user.address.line1" size="33" required> <br>
<input type="text" ng-model="user.address.city" size="12" required>,
<input type="text" ng-model="user.address.state"
ng-pattern="state" size="2" required>
<input type="text" ng-model="user.address.zip" size="5"
ng-pattern="zip" required><br><br>
<label>Phone:</label>
[ <a href="" ng-click="addContact()">add</a> ]
<div ng-repeat="contact in user.contacts">
<select ng-model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng-model="contact.value" required>
[ <a href="" ng-click="removeContact(contact)">X</a> ]
</div>
<hr/>
Debug View:
<pre>user={{user | json}}</pre>
</div>
</script>
</div>
<div class="tab-pane" title="script.js">
<pre class="prettyprint linenums" ng-set-text="script.js-6"></pre>
<script type="text/ng-template" id="script.js-6">
function FormController($scope) {
var user = $scope.user = {
name: 'John Smith',
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
contacts:[{type:'phone', value:'1(234) 555-1212'}]
};
$scope.state = /^\w\w$/;
$scope.zip = /^\d\d\d\d\d$/;
$scope.addContact = function() {
user.contacts.push({type:'email', value:''});
};
$scope.removeContact = function(contact) {
for (var i = 0, ii = user.contacts.length; i < ii; i++) {
if (contact === user.contacts[i]) {
$scope.user.contacts.splice(i, 1);
}
}
};
}
</script>
</div>
<div class="tab-pane" title="End to end test">
<pre class="prettyprint linenums" ng-set-text="scenario.js-8"></pre>
<script type="text/ng-template" id="scenario.js-8">
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[ng\\:model="user.address.zip"]').
prop('className')).not().toMatch(/ng-invalid/);
using('.example').input('user.address.zip').enter('abc');
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).toMatch(/ng-invalid/);
});
it('should validate state', function() {
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.not().toMatch(/ng-invalid/);
using('.example').input('user.address.state').enter('XXX');
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.toMatch(/ng-invalid/);
});
</script>
</div>
</div><h3>Demo</h3>
<div class="well doc-example-live" ng-embed-app="" ng-set-html="index.html-7" ng-eval-javascript="script.js-6"></div>
<h2>Things to notice</h2>
<ul>
<li>The user data model is initialized <a href="api/ng.directive:ngController"><code>controller</code></a> and is
available in the <a href="api/ng.$rootScope.Scope"><code>scope</code></a> with the initial data.</li>
<li>For debugging purposes we have included a debug view of the model to better understand what
is going on.</li>
<li>The <a href="api/ng.directive:input"><code>input directives</code></a> simply refer
to the model and are data-bound.</li>
<li>The inputs validate. (Try leaving them blank or entering non digits in the zip field)</li>
<li>In your application you can simply read from or write to the model and the form will be updated.</li>
<li>By clicking the 'add' link you are adding new items into the <code>user.contacts</code> array which are then
reflected in the view.</li>
</ul></div>

View file

@ -0,0 +1,52 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><h3>Source</h3>
<div source-edit="" source-edit-deps="angular.js script.js" source-edit-html="index.html-10" source-edit-css="" source-edit-js="script.js-9" source-edit-unit="" source-edit-scenario="scenario.js-11"></div>
<div class="tabbable"><div class="tab-pane" title="index.html">
<pre class="prettyprint linenums" ng-set-text="index.html-10" ng-html-wrap=" angular.js script.js"></pre>
<script type="text/ng-template" id="index.html-10">
<div ng-controller="HelloCntl">
Your name: <input type="text" ng-model="name" value="World"/>
<hr/>
Hello {{name}}!
</div>
</script>
</div>
<div class="tab-pane" title="script.js">
<pre class="prettyprint linenums" ng-set-text="script.js-9"></pre>
<script type="text/ng-template" id="script.js-9">
function HelloCntl($scope) {
$scope.name = 'World';
}
</script>
</div>
<div class="tab-pane" title="End to end test">
<pre class="prettyprint linenums" ng-set-text="scenario.js-11"></pre>
<script type="text/ng-template" id="scenario.js-11">
it('should change the binding when user enters text', function() {
expect(binding('name')).toEqual('World');
input('name').enter('angular');
expect(binding('name')).toEqual('angular');
});
</script>
</div>
</div><h3>Demo</h3>
<div class="well doc-example-live" ng-embed-app="" ng-set-html="index.html-10" ng-eval-javascript="script.js-9"></div>
<h2>Things to notice</h2>
<p>Take a look through the source and note:</p>
<ul>
<li>The script tag that <a href="guide/bootstrap">bootstraps</a> the Angular environment.</li>
<li>The text <a href="api/ng.directive:input"><code>input form control</code></a> which is
bound to the greeting name text.</li>
<li>There is no need for listener registration and event firing on change events.</li>
<li>The implicit presence of the <code>name</code> variable which is in the root <a href="api/ng.$rootScope.Scope"><code>scope</code></a>.</li>
<li>The double curly brace <code>{{markup}}</code>, which binds the name variable to the greeting text.</li>
<li>The concept of <a href="guide/dev_guide.templates.databinding">data binding</a>, which reflects any
changes to the
input field in the greeting text.</li>
</ul></div>

View file

@ -0,0 +1,50 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>Welcome to the Angular cookbook. Here we will show you typical uses of Angular by example.</p>
<h2>Hello World</h2>
<p><a href="cookbook/helloworld">Hello World</a>: The simplest possible application that demonstrates the
classic Hello World!</p>
<h2>Basic Form</h2>
<p><a href="cookbook/form">Basic Form</a>: Displaying forms to the user for editing is the bread and butter
of web applications. Angular makes forms easy through bidirectional data binding.</p>
<h2>Advanced Form</h2>
<p><a href="cookbook/advancedform">Advanced Form</a>: 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.</p>
<h2>Model View Controller</h2>
<p><a href="cookbook/mvc">MVC</a>: 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.</p>
<h2>Multi-page App and Deep Linking</h2>
<p><a href="cookbook/deeplinking">Deep Linking</a>: 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.</p>
<p>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.</p>
<h2>Services</h2>
<p><a href="api/ng">Services</a>: 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).</p>
<h2>External Resources</h2>
<p><a href="cookbook/buzz">Resources</a>: 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.</p></div>

View file

@ -0,0 +1,138 @@
<h1><code ng:non-bindable=""></code>
<span class="hint"></span>
</h1>
<div><p>MVC allows for a clean and 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.</p>
<p>The model is a set of objects and primitives that are referenced from the Scope ($scope) object.
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.</p>
<h3>Source</h3>
<div source-edit="" source-edit-deps="angular.js script.js" source-edit-html="index.html-13" source-edit-css="" source-edit-js="script.js-12" source-edit-unit="" source-edit-scenario="scenario.js-14"></div>
<div class="tabbable"><div class="tab-pane" title="index.html">
<pre class="prettyprint linenums" ng-set-text="index.html-13" ng-html-wrap=" angular.js script.js"></pre>
<script type="text/ng-template" id="index.html-13">
<h4>Tic-Tac-Toe</h4>
<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>
</script>
</div>
<div class="tab-pane" title="script.js">
<pre class="prettyprint linenums" ng-set-text="script.js-12"></pre>
<script type="text/ng-template" id="script.js-12">
function TicTacToeCntl($scope, $location) {
$scope.cellStyle= {
'height': '20px',
'width': '20px',
'border': '1px solid black',
'text-align': 'center',
'vertical-align': 'middle',
'cursor': 'pointer'
};
$scope.reset = function() {
$scope.board = [
['', '', ''],
['', '', ''],
['', '', '']
];
$scope.nextMove = 'X';
$scope.winner = '';
setUrl();
};
$scope.dropPiece = function(row, col) {
if (!$scope.winner && !$scope.board[row][col]) {
$scope.board[row][col] = $scope.nextMove;
$scope.nextMove = $scope.nextMove == 'X' ? 'O' : 'X';
setUrl();
}
};
$scope.reset();
$scope.$watch(function() { return $location.search().board;}, readUrl);
function setUrl() {
var rows = [];
angular.forEach($scope.board, function(row) {
rows.push(row.join(','));
});
$location.search({board: rows.join(';') + '/' + $scope.nextMove});
}
function grade() {
var b = $scope.board;
$scope.winner =
row(0) || row(1) || row(2) ||
col(0) || col(1) || col(2) ||
diagonal(-1) || diagonal(1);
function row(row) { return same(b[row][0], b[row][1], b[row][2]);}
function col(col) { return same(b[0][col], b[1][col], b[2][col]);}
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 : '';};
}
function readUrl(value) {
if (value) {
value = value.split('/');
$scope.nextMove = value[1];
angular.forEach(value[0].split(';'), function(row, col){
$scope.board[col] = row.split(',');
});
grade();
}
}
}
</script>
</div>
<div class="tab-pane" title="End to end test">
<pre class="prettyprint linenums" ng-set-text="scenario.js-14"></pre>
<script type="text/ng-template" id="scenario.js-14">
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();
}
</script>
</div>
</div><h3>Demo</h3>
<div class="well doc-example-live" ng-embed-app="" ng-set-html="index.html-13" ng-eval-javascript="script.js-12"></div>
<h2>Things to notice</h2>
<ul>
<li>The controller is defined in JavaScript and has no reference to the rendering logic.</li>
<li>The controller is instantiated by Angular and injected into the view.</li>
<li>The controller can be instantiated in isolation (without a view) and the code will still execute.
This makes it very testable.</li>
<li>The HTML view is a projection of the model. In the above example, the model is stored in the
board variable.</li>
<li>All of the controller's properties (such as board and nextMove) are available to the view.</li>
<li>Changing the model changes the view.</li>
<li>The view can call any controller function.</li>
<li>In this example, the <code>setUrl()</code> and <code>readUrl()</code> 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 <a href="api/ng.$rootScope.Scope#$watch"><code>$watch()</code></a> to set up a listener that invokes <code>readUrl()</code> when needed.</li>
</ul></div>