ここで作ったTODOアプリを改造する。今まで一つの画面だけで完結していたTODOアプリをあえてデータ受け渡しの伴う画面遷移があるアプリに作り変えてみる。
画面は二つ、TODO一覧画面とTODO入力画面だ。
まずはプロジェクト構成から。Chrome Packaged Appsなアプリのための構成になっている。
manifest.json
angular.min.js
background.js
app.js
index.html
list.html
detail.html
todo.css
calculator-128.png
calculator-64.png
今回重要なのは太字の箇所。それ以外は前回と同じ。
まずはindex.htmlから見てみよう。
<html ng-app="todo" ng-csp>
<head>
<script src="angular.min.js"></script>
<script src="app.js"></script>
<link rel="stylesheet" href="todo.css">
</head>
<body>
<h2>Todo</h2>
<div ng-view></div>
</body>
</html>
ng-app="todo" でアプリ名を指定。
app.js にはコントローラーが記述されている。
画面遷移すると ng-view の部分が切り替わる。
次は一覧画面 list.html
<span>{{remaining()}} of {{todos.length}} remaining</span>
[ <a href="" ng-click="archive()">archive</a> ]
<br/>
<input type="text" ng-model="search" class="search-query" placeholder="Search">
<table>
<thead>
<tr>
<th>Todo</th>
<th><a href="#/new">(New)</a></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="todo in todos | filter:search | orderBy:'text'">
<td>
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</td>
<td></td>
</tr>
</tbody>
</table>
基本的には前回のTODOアプリと変わらないのだが、今回は、
<input type="text" ng-model="search" class="search-query" placeholder="Search">
と
<tr ng-repeat="todo in todos | filter:search | orderBy:'text'">
でフィルタとソートが実装されていたり、
入力画面へ飛ぶためのリンク
<th><a href="#/new">(New)</a></th>
があったりする。
次に入力画面 detail.html
<form name="myForm">
<div class="control-group" ng-class="{error: myForm.todoText.$invalid}">
<label>TODO</label>
<input type="text" name="todoText" ng-model="todoText" placeholder="add new todo here" size="30" required>
<span ng-show="myForm.todoText.$error.required" class="help-inline">Required</span>
</div>
<br>
<a href="#/" class="btn btn-cancel">Cancel</a>
<button ng-click="addTodo()" ng-disabled="myForm.$invalid" class="btn btn-primary">Add</button>
</form>
<form name="myForm">
でフォーム名"myForm"を定義し、
<div class="control-group" ng-class="{error: myForm.todoText.$invalid}">
<input type="text" name="todoText" ng-model="todoText" placeholder="add new todo here" size="30" required>
<span ng-show="myForm.todoText.$error.required" class="help-inline">Required</span>
</div>
でテキストボックスに“入力必須”のバリデーション required を組み込んでいる。
まー特に説明の必要もないだろう。
では最後に最も重要なコントローラーの実装を見てみよう。
app.js
angular.module('todo', []).
config(function($routeProvider) {
$routeProvider.
when('/', {controller:ListCtrl, templateUrl:'list.html'}).
when('/new', {controller:CreateCtrl, templateUrl:'detail.html'}).
otherwise({redirectTo:'/'});
});
function ListCtrl($scope, $rootScope) {
$scope.remaining = function() {
var count = 0;
angular.forEach($rootScope.todos, function(todo) {
count += todo.done ? 0 : 1;
});
return count;
};
$scope.archive = function() {
var oldTodos = $rootScope.todos;
$rootScope.todos = [];
angular.forEach(oldTodos, function(todo) {
if (!todo.done) $rootScope.todos.push(todo);
});
};
}
function CreateCtrl($scope, $rootScope, $location) {
$rootScope.addTodo = function() {
if(!$rootScope.todos){
$rootScope.todos = [];
}
$rootScope.todos.push({text:$scope.todoText, done:false});
$location.path('/');
};
}
1行目でアプリ名"todo"を指定し、2行目からrouteProviderを設定している。そこでは、URIとそれに紐づくコントローラーや画面テンプレートを定義している。
"/" が呼ばれたら "ListCtrl" が実行されて、 "list.html" が描画される、、、みたいな感じ。
そこから下はコントローラーの実装。今回はListCtrl(一覧画面用)とCreateCtrl(入力画面用)の二つだけ。
前回のTODOアプリと違うのは、TODO一覧の値 "todos" を $scope ではなく $rootScope で参照していること。こうすることで画面を跨いだ todos の受け渡しが可能となる。
CreateCtrlの最後の $location.path('/'); という行は入力画面コントローラー処理から抜けるために飛び先 "/" を指定している。
さらりと説明したが、今回最も大切なのは app.js の中の routeProvider の設定と $rootScope である。自分は$rootScopeの存在を知らずに少しハマってました。
参考
NEXTCODE BLOG: AngularJSでController間の通信を行う
Angular.jsについてちょっとしゃべる - slideshare
関連エントリ
JS用MVCフレームワーク「AngularJS」を使ってChrome Packaged Apps【基本編】
【Chrome】Packaged Apps アプリをHelloWorld【Google】
スマホOSの主導権争いの行方