Help us understand the problem. What is going on with this article?

Backbone.jsを使用したTODOリストアプリの実装 - AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第2回】マニュアル

More than 5 years have passed since last update.

はじめに

本投稿は、2015/2/27に行われたAWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第2回】の内容について後日まとめた資料です。

※ 参加された方へ。

当日は諸般の事情により当資料の準備が出来ず、GitHub上のソースを口頭で説明しながらとなってしまいました。
わかりにくかったと思います。申し訳ありません。
当日、もっとこう説明すればよかった、こういう手順が良かった、など反省しながら書きました。
この資料を元にもう一回やってみてフィードバックもらえると非常にありがたいです!
当勉強会は全12回、良くなかったところはどんどん改善していきますので、宜しくお願いいたします!

※ 資料の記述について

いろいろ作業手順があります。解説はけっこう細かく書いています。
作業としてやらないといけないところは:white_check_mark:で表示しています。

SSHログインのやり方等、前回やったことはAWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第1回】の当日実施内容まとめ - Qiitaから確認して下さい。

:warning: 2015/3/13追記 : 各lessonにGitにコミットする作業と、最後にpushする作業を追記しました。

前回までの成果確認

前回はEC2インスタンスを作成してサーバを作り、最低限のAPIを作成するところまでやりました。
まず下記2つをやって動作確認しておきましょう。

  • :white_check_mark:EC2インスタンス起動
  • :white_check_mark:API動作確認(POSTMAN使用)

今回の内容

今回はbackbone.jsを使用し、前回作成したサーバ側のAPIを呼び出してシンプルなTODOアプリを作成するところまでやります。

  • 事前準備
  • Lesson 1 ルーティング
  • Lesson 2 MVC基本動作
  • Lesson 3 一覧画面表示
  • Lesson 4 一件追加
  • Lesson 5 チェックボックス更新
  • Lesson 6 一件削除
  • Lesson 7 更新画面を表示
  • Lesson 8 更新処理

の順に進めていきます。
その前に、backbone.jsについて基礎的なことを確認しておきましょう。

backbone.jsとは?

クライアント側のMVCフレームワークです。
今回backboneを使用しますが、そもそも何をしてくれて、使うと何が嬉しいんでしょうか?

ライブラリやフレームワークを使用しなかった場合と比較すると、

  • いわゆるMVCパターンをクライアント側で実現できる
  • ソースの記述量が減る
  • ソースの見通しが良くなる

といったあたりかと思います。
※ backboneを使用しても、大規模開発になると気を付けないとやっぱりカオスになりますが...

なぜbackbone.js?

backbone.jsは、まず「軽い」という特徴があります。
MVCの骨組みだけを提供する、まさに背骨(backbone)ですので、比較的学習コストが低く、他のライブラリとの併用もやりやすく、本体にはない機能もいろいろなプラグインを利用して実現できます(第3回で取り上げる予定のmarionetteとか)。
今回は基礎的な内容のみですが、基礎を抑えておけば大規模開発にも対応できると思います。

巷ではAngularJSが流行ってますし、React.jsなんかもキてますが、その前に比較的シンプルであるbackboneでクライアントMVCの基礎を学んでおくことで、他のフレームワーク使用する場合でも、それらを学ぶ際の土台となるノウハウが得られると思います。
※ Reactなんかはbackboneと連携してもいいと思います(調べてみよう...)。

backboneの構成要素

backboneの構成要素について具体的に見ていきます。
MVCの各役割を、backboneでは下記のコンポーネントがそれぞれ担っています。

M/V/C Backboneのコンポーネント
Model Backbone.Model, Backbone.Collection
View Backbone.View, HTMLテンプレート※
Controller Backbone.View, Backbone.Router

※HTMLテンプレートはbackboneの構成要素ではありませんが、説明のため便宜的に入れています。

図にするとこんな感じです。

mvc.jpg

なんかちょっと違和感ありますね。
ひとつづつ見ていきます。

Model

Modelは、Backbone.ModelとBackbone.Collectionが担います。

コントローラからの操作により下記を行います。

  • Backbone.Model
    • データ一件に対して何かする。
  • Backbone.Collection
    • データ(Backbone.Model)をまとめて扱う。

完了するとイベントを発火します。

ともに、サーバとREST APIを通してデータのやり取りをします。
Modelに関してはわかりやすいかと思います。

View

Viewは、Backbone.ViewとHTMLテンプレートが担います。

モデルからのイベントを受け取って動くのが基本です。

  • Backbone.View
    • 表示のためのロジック
  • HTMLテンプレート
    • ブラウザに表示されるHTMLの断片

MVCにおけるViewの役割は、Modelの変更を検知して表示することがメインです。
Backbone.Viewが、HTMLテンプレートを部品として使用して表示ロジックを担う、という理解でいいと思います。

Viewも違和感はないかと思います。

Controller

Controllerは、Backbone.ViewとBackbone.Routerが担います。
違和感の正体はここですね。
Viewに入っていたBackbone.ViewがControllerも担っている?
これについて説明します。

  • Backbone.View
    • DOMからのイベント(ユーザの操作など)を拾って関数実行
    • Backbone.Model, Backbone.Collectionからのイベントを拾って関数実行

つまり、Backbone.Viewは、「View」と言う名前ながら、

  • イベントを拾って処理の流れをコントロールするというControllerの役割
  • Modelの変更時に表示内容を更新するというViewの役割

の2つを兼ねているわけです。
図では、時計回りに綺麗にイベント、操作が単方向に伝播していますが、実際にはViewが2箇所に登場していたのでそんなに単純じゃなかったのです。
これは、「Backboneはそういうものだ」と理解しましょう!

Backbone.Routerは、

  • URLの変更を検知して関数実行

という役割を担います。いわゆるルーティングです。
ルーティングを「コントローラ」に含めるかどうかは微妙ですが、ここではこうしておきましょう。

  • Backbone.Routerが、「URL変更イベント」を拾って画面を遷移させる=Viewを消去し、新しいViewを表示する
  • Backbone.Viewが「画面内のイベントを拾って処理する」

という理解でいいと思います。

Viewについては、

Backbone.ViewはViewでもありControllerでもある

と納得してしまいましょう!

事前準備

SSHでログイン

ともかくログインします。sshログインから始まります!

  • :white_check_mark: ssh -i [秘密鍵のパス] study@[サーバのPublicIP]

ディレクトリを移動しておきます。

  • :white_check_mark: cd /var/www/study/rest-study

では、はじめましょう!

gitのブランチを整えておく

前回は、vol/01ブランチを作成し、そこでソースを作成し、commit, pushし、GitHub上でそのvol/01が確認できるところまでやりました。
下記の手順でブランチを整えておきます。

第1回用のbranch(vol/01)をmasterにmergeしておく

  • :white_check_mark: masterブランチをチェックアウト git checkout master
  • :white_check_mark: vol/01ブランチをmasterにマージ git merge vol/01
実行結果
[study@ip-172-31-8-2 rest-study]$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
[study@ip-172-31-8-2 rest-study]$ git merge vol/01
Updating d234729..55ef625
Fast-forward
 app/Config/routes.php                  |  7 +++++++
 app/Controller/AppController.php       |  5 ++++-
 app/Controller/TodoListsController.php | 42 ++++++++++++++++++++++++++++++++++++++++++
 app/Model/TodoList.php                 |  6 ++++++
 4 files changed, 59 insertions(+), 1 deletion(-)
 create mode 100644 app/Controller/TodoListsController.php
 create mode 100644 app/Model/TodoList.php
[study@ip-172-31-8-2 rest-study]$ 

これで、masterブランチに前回の成果が反映されました。

GitHubにpushしてGitHub側も最新にしておく

:white_check_mark: git push origin master -> ユーザ名、パスワードを入力

実行結果
[study@ip-172-31-8-2 rest-study]$ git push origin master
Username for 'https://github.com': ks-ocean
Password for 'https://ks-ocean@github.com':
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/ks-ocean/rest-study.git
   d234729..55ef625  master -> master

:white_check_mark: GitHubの画面を確認しておく。
https://github.com/[ユーザ名]/rest-study/commits/masterで、vol/01のブランチの作業内容がマージされていることが下図の通り確認できます。

github1.jpg

fork元のリポジトリと同期をとっておく

fork元のリポジトリはここ

  • :white_check_mark: fork元のリポジトリをupstreamという名前で追加します。
    • git remote add upstream https://github.com/suzukishouten-study/rest-study.git
  • :white_check_mark: fork元リポジトリupstreamの内容を取得します。
    • git fetch upstream
  • :white_check_mark: 確認します。
    • git branch -av
実行結果
[study@ip-172-31-8-2 rest-study]$ git remote add upstream https://github.com/suzukishouten-study/rest-study.git
[study@ip-172-31-8-2 rest-study]$ git fetch upstream
[study@ip-172-31-8-2 rest-study]$ git branch -av
* master                         55ef625 vol/01 complete
  vol/01                         55ef625 vol/01 complete
  remotes/origin/HEAD            -> origin/master
  remotes/origin/master          55ef625 vol/01 complete
  remotes/origin/vol/01          55ef625 vol/01 complete
  remotes/origin/vol/01-finish   59d8146 add simple api
  remotes/upstream/master        d234729 first commmit
  remotes/upstream/vol/01-finish 59d8146 add simple api
  remotes/upstream/vol/02-finish ec54cf5 8 更新処理

一番下に、remotes/upstream/vol/02-finishというブランチが追加されています。
これは、今回の内容を全て終えた状態のものを事前にfork元リポジトリに用意しておいたものです。
このブランチをチェックアウトすれば、今回の完了状態のアプリケーションの動作を確認することができます。

GitHub上でも、

  • ここvol/02-finishブランチのコミット一覧
  • ここで今回のLesson2の完成状態のソースやDiff表示

を見ることが出来ます。参考にしてください。

今回の作業用のbranchを作成

今回の作業用ブランチとして、vol/02ブランチを作り、チェックアウトします。

  • :white_check_mark: git branch vol/02 でブランチを作って
  • :white_check_mark: git checkout vol/02 でそのブランチに変更
実行結果
#(確認のための git branch が入ってます)
[study@ip-172-31-8-2 rest-study]$ git branch vol/02
[study@ip-172-31-8-2 rest-study]$ git branch
* master
  vol/01
  vol/02
[study@ip-172-31-8-2 rest-study]$ git checkout vol/02
Switched to branch 'vol/02'
[study@ip-172-31-8-2 rest-study]$ git branch
  master
  vol/01
* vol/02
[study@ip-172-31-8-2 rest-study]$

これで準備は整いました。
さあ、作業を始めましょう!

まずはライブラリ取得

今回使用する、Backbone, underscore, jqueryのファイルを公式サイトから取得します。
下記の手順でやっていきましょう。

ディレクトリ作成

まずは配置先のディレクトリを作ります。

  • :white_check_mark: mkdir app/webroot/js/libを実行して作成先ディレクトリ作成
  • :white_check_mark: cd app/webroot/js/libを実行して移動し、次の手順へ進みましょう。

各ライブラリをダウンロード

wgetコマンドを使用してダウンロードします。
ダウンロードはそれぞれ下記URLからです(全て公式サイト)。

  • backbone http://backbonejs.org/backbone-min.js
  • underscore http://underscorejs.org/underscore-min.js
  • jquery http://code.jquery.com/jquery-2.1.3.min.js

コマンドは下記の通り。

  • :white_check_mark: wget http://backbonejs.org/backbone-min.js
  • :white_check_mark: wget http://underscorejs.org/underscore-min.js
  • :white_check_mark: wget http://code.jquery.com/jquery-2.1.3.min.js
実行結果
[study@ip-172-31-8-2 lib]$ wget http://backbonejs.org/backbone-min.js
--2015-03-05 11:49:33--  http://backbonejs.org/backbone-min.js
backbonejs.org (backbonejs.org) をDNSに問いあわせています... 192.30.252.154, 192.30.252.153
backbonejs.org (backbonejs.org)|192.30.252.154|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 302 Found
場所: /backbone-min.js [続く]
--2015-03-05 11:49:34--  http://backbonejs.org/backbone-min.js
backbonejs.org (backbonejs.org)|192.30.252.154|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 19999 (20K) [application/javascript]
`backbone-min.js' に保存中

100%[================================================================>] 19,999       111KB/s 時間 0.2s

2015-03-05 11:49:34 (111 KB/s) - `backbone-min.js' へ保存完了 [19999/19999]

[study@ip-172-31-8-2 lib]$ wget http://underscorejs.org/underscore-min.js
--2015-03-05 11:49:34--  http://underscorejs.org/underscore-min.js
underscorejs.org (underscorejs.org) をDNSに問いあわせています... 192.30.252.154, 192.30.252.153
underscorejs.org (underscorejs.org)|192.30.252.154|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 16523 (16K) [application/javascript]
`underscore-min.js' に保存中

100%[================================================================>] 16,523      89.8KB/s 時間 0.2s

2015-03-05 11:49:35 (89.8 KB/s) - `underscore-min.js' へ保存完了 [16523/16523]

[study@ip-172-31-8-2 lib]$ wget http://code.jquery.com/jquery-2.1.3.min.js
--2015-03-05 11:49:35--  http://code.jquery.com/jquery-2.1.3.min.js
code.jquery.com (code.jquery.com) をDNSに問いあわせています... 94.31.29.53, 94.31.29.230
code.jquery.com (code.jquery.com)|94.31.29.53|:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 84320 (82K) [application/x-javascript]
`jquery-2.1.3.min.js' に保存中

100%[================================================================>] 84,320       389KB/s 時間 0.2s

2015-03-05 11:49:35 (389 KB/s) - `jquery-2.1.3.min.js' へ保存完了 [84320/84320]

[study@ip-172-31-8-2 lib]$ ls -l
合計 124
-rw-r--r-- 1 study wheel 19999  2月  4 01:34 backbone-min.js
-rw-r--r-- 1 study wheel 84320 12月 19 00:17 jquery-2.1.3.min.js
-rw-r--r-- 1 study wheel 16523  2月 22 23:14 underscore-min.js
[study@ip-172-31-8-2 lib]$

ライブラリの準備まで出来ました。
これ以降、Lesson1〜8まではソースを編集していきます。
Lessonが進むに従い、少しずつ機能が追加され、lesson8で完成します。
各lessonは、

  • Lesson完了後のアプリの動作
  • 編集するファイル一覧
  • 各ファイルの編集内容とポイント解説

というフォーマットで説明していきます。
※各ファイルの編集内容とポイント解説はかなり細かいので、頑張って読みましょう!

では、Lesson1です!

Lesson 1 ルーティング

Backbone.Routerの動きをまずは見ていきます。

Lesson完了後のアプリの動作

http://[PublicIP]/rest-study/#todo-lists
にアクセスすると、
lesson1-1.jpg
こんな感じで表示されます。
Backbone.Routerの働きで、#todo-listsにアクセスすると「TODO一覧表示」と表示する関数が動いています。

http://[PublicIP]/rest-study/#todo-lists/1
にアクセスすると、
lesson1-2.jpg

こんな感じで表示されます。
Backbone.Routerの働きで、#todo-listsにアクセスすると「id = X のTODO詳細表示」と表示する関数が動いています。

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
追加 app/webroot/js/routers/router.js ルータ(Controller)
追加 app/webroot/js/app.js アプリケーションの開始ポイント

各ファイルの編集内容とポイント解説

default.ctp

HTMLテンプレートです。
CakePHPでは、標準でこのdefault.ctpが表示されます。
今回はこれをまるっと書き換えてHTMLテンプレートとして使用します。

default.ctp
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>シンプルTODOアプリ</title>
</head>
<body>

    <!-- ① js(library) -->
    <script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
    <script src="js/lib/underscore-min.js" type="text/javascript"></script>
    <script src="js/lib/backbone-min.js" type="text/javascript"></script>

    <!-- js(application) -->
    <!--  ② router   -->
    <script src="js/routers/router.js" type="text/javascript"></script>
    <!--  ③ entry point   -->
    <script src="js/app.js" type="text/javascript"></script>

</body>
</html>

コメント中の①〜③でそれぞれ、

  • ① jquery, underscore, backboneの各ライブラリを読み込んでいます。
  • ② router.jsを読み込んでいます。
  • ③ app.jsを読み込んでいます。

router.js

ルーティングを行っている本体です。

router.js
var app = app || {};

//router
(function(app) {
    app.TodoRouter = Backbone.Router.extend({
        routes : {
            ''                  : 'todoLists',  
            'todo-lists'        : 'todoLists',  
            'todo-lists/:id'    : 'todoDetail'
        },
        todoLists : function() {
            alert('TODO一覧表示');
        },
        todoDetail : function(id) {
            alert('id = ' + id + ' のTODO詳細表示');
        },
    });
})(app);

※名前空間を使用しています。
上記の
var app = app || {};
(function(app) {
})(app);
といった記述がそれですが、ここに記載のやり方を採用しています。他のソースファイルも全て同様の方法を採用しています。

  • app.TodoRouter = Backbone.Router.extend({
    この記述ですが、backboneが持っているBackbone.Routerオブジェクトを継承し、その継承した結果のオブジェクトをapp.TodoRouterに設定する処理です。
    この先、コレクション、モデル、ビューで同様の記述が出てきますが、全てbackboneが持っているオブジェクトを継承して定義する、という意味になります。

  • routes{}変数
    ここでは、URLと関数のペアを記述します。
    URLがtodo-listsならtodoLists関数を実行する、という意味です。

  • todoLists関数
    routes{}で記載したtodoLists関数の本体です。ここで先ほど見たメッセージを表示するalert()関数を実行しています。

  • todoDetail関数
    同じくroutes{}で記載した関数の本体です。

app.js

先のdefault.ctpの中で最後に読み込まれていますので、必要なライブラリとrouter.jsを既に読み込んだ状態です。

app.js
var app = app || {};

//開始
(function(app) {
    var todoRouter = new app.TodoRouter();  // ①
    Backbone.history.start();               // ②
})(app);

①でapp.TodoRouterのインスタンスを作成しています。
②でBackbone.history.start();とやってますが、下記に解説します。

#付きのURL(正確には「URLフラグメント」)はページ内の位置を指す際に使用されますが、変更された場合、hashchangeイベントが発生します。素のjavascriptだと、下記のようwindow.onhashchangeにハンドラを書いてあげればhashchangeイベント発生時に関数を実行することができます。

window.onhashchange = function (e){
    alert('hash change!');
};

Backbone.history.start()は、backboneにhashchangeイベントの監視を開始させる、という意味を持ちます。これを実行することで、先ほどのrouter.jsのroutes{}に設定した内容が生きてくる、というわけです。

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り作成。
  • :white_check_mark: app/webroot/js/routers/router.js を上記の通り作成。
  • :white_check_mark: app/webroot/js/app.js を上記の通り作成。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

Lesson2へ!

Lesson 2 MVC基本動作

Lesson1ではまずrouterの動きだけ見ました。
Lesson2では、Model, View, Controllerの動きの基本をすべて見てみます。

Lesson完了後のアプリの動作

画面上でわかりやすく見ることが難しいので、console.log()を使用してログを出力し、内容を検証していきます。

http://[PublicIP]/rest-study/#todo-lists
にアクセスし、下図の通りchromeのデベロッパーツールを表示します。

lesson2-1.jpg

※ここで、「ソースマップ機能」を無効にしておきます。
ダウンロードしたbackboneやunderscoreなどは、「ソースマップ」機能に対応していて、ソースはminifyされていますが、chrome上で設定すればminify前のような状態で参照することが出来ます。
設定しておかないとコンソールにエラーログが出てしまいます(動作上問題はないですが)ので、今回はこのへんは置いといて、chromeのソースマップ機能はOFFにします。
まず、デベロッパーツールの右上の歯車アイコンをクリックして設定を表示。

lesson2-3.jpg

下記のチェックボックスをOFFにすればOKです。
lesson2-4.jpg

では動作確認。
下図の「ここをクリックしてクリア」でいったんログを削除し、ページを再ロードします。
consoleタブに、プログラム中でconsole.log()した部分が表示されます。

lesson2-2.jpg

ログの内容を解説します。

No. ソース ログ
1 router.js:13 Todo一覧表示用ビューにルーティング
2 todo-collection-view.js:8 Todo一覧表示用ビュー初期化
3 todo-collection-view.js:14 Todo一覧表示用ビュー表示処理
4 todo-collection-view.js:16 コレクションをフェッチ
5 jquery-2.1.3.min.js XHR finished loading: GET "http://[PublicIP]/rest-study/todo_lists.json".
6 todo-collection.js:11 コレクションをパース
7 todo-model.js:9 モデルをパース
8 todo-model.js:10 ▶ Object {TodoList: Object}
9 todo-model.js:9 モデルをパース
10 todo-model.js:10 ▶ Object {TodoList: Object}
11 todo-model.js:9 モデルをパース
12 todo-model.js:10 ▶ Object {TodoList: Object}

解説
※この例では、予めデータを3件登録してあります。

  1. rest-study/#todo-listsにアクセスしたことをrouterが検知し、Todo一覧表示用ビューを起動した
  2. Todo一覧表示用ビューの初期化処理が実行され、ビューが実体化
  3. Todo一覧表示用ビューの表示処理が開始される
  4. コレクションのフェッチ処理を実行、サーバ側APIへのアクセス開始
  5. jquery内でajax通信が実行され、http://[PublicIP]/rest-study/todo_lists.jsonにGetメソッドでアクセスし正常にデータを取得完了
  6. コレクション内で、5で取得したデータの解析(パース)処理が実行
  7. コレクション内の最初のデータ(モデル)の解析(パース)処理が実行
  8. 7の解析処理内で、取得したデータをダンプ出力(▶をクリックするとダンプの詳細が見れます。)
  9. コレクション内の2番目のデータの解析処理が実行
  10. 8同様取得したデータをダンプ
  11. コレクション内の3番目のデータの解析処理が実行
  12. 8同様取得したデータをダンプ

詳しくは、各ソースの解説で見ていきます。

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
修正 app/webroot/js/routers/router.js ルータ(Controller)
追加 app/webroot/js/views/todo-collection-view.js ビュー(Todoリスト一覧)
追加 app/webroot/js/collections/todo-collection.js コレクション(Todo全件)
追加 app/webroot/js/models/todo-model.js モデル(Todo一件)

各ファイルの編集内容とポイント解説

これ以降、修正したファイルについては差分のDiff表示で示します。

  • 「+」記号が追加行
  • 「-」記号が削除行
  • 何もない行は変更のなかった行(ファイル内の位置をわかりやすくするための表示)

です。

以降、各ファイルを淡々と解説しますが、上記のログと実装を確認しつつ、解析していきましょう。

default.ctp

default.ctp
    <script src="js/lib/backbone-min.js" type="text/javascript"></script>

    <!-- js(application) -->
+   <!--   model   -->
+   <script src="js/models/todo-model.js" type="text/javascript"></script>
+   <!--   collection   -->
+   <script src="js/collections/todo-collection.js" type="text/javascript"></script>
+   <!--   view   -->
+   <script src="js/views/todo-collection-view.js" type="text/javascript"></script>
    <!--   router   -->
    <script src="js/routers/router.js" type="text/javascript"></script>
    <!--   entry point   -->

追加したmodel, collection, view の各ファイル、

  • todo-model.js
  • todo-collection.js
  • todo-collection-view.js

の各ファイルの読み込みを追加しています。

router.js

router.js
            'todo-lists/:id'    : 'todoDetail'
        },
        todoLists : function() {
-           alert('TODO一覧表示');
+           //Todo一覧表示用ビューにルーティング
+           console.log("Todo一覧表示用ビューにルーティング");
+           new app.TodoCollectionView();
        },

        todoDetail : function(id) {
  • Lesson1で表示したalertはいらないので削除
  • ログ No.1で表示されていた内容はここでconsole.log()で表示
  • app.TodoCollectionViewを初期化

ルーティングにより、Lesson1同様このtodoLists関数が実行され、ログ出力とTodo一覧表示用ビューの初期化を行っています。これで、Todo一覧表示用ビューの実態であるtodo-collection-view.js内の初期化処理が実行を開始します。

todo-collection-view.js

Todo一覧表示用ビューの実装です。

todo-collection.js
var app = app || {};

//Todo一覧表示用ビュー
(function(app) {
    app.TodoCollectionView = Backbone.View.extend({
        todoCollection : {},
        initialize : function() {
            console.log("Todo一覧表示用ビュー初期化");
            //コレクションを生成
            this.todoCollection = new app.TodoCollection()
            this.render();
        },
        render : function() {
            console.log("Todo一覧表示用ビュー表示処理");
            //コレクションをフェッチ
            console.log("コレクションをフェッチ");
            this.todoCollection.fetch();
            return this;
        },
    })
})(app);
  • router内でnew app.TodoCollectionView()としたタイミングで、initialize関数が実行
    • ログ No.2を表示
    • app.TodoCollectionを初期化し、ローカル変数のtodoCollectionに格納
    • render()関数を実行
  • render()関数内の処理
    • ログ No.3を表示
    • todoCollectionのfetch()関数(サーバのAPIを呼び出す)を実行

todo-collection.js

TODOの一覧を扱う、コレクション(モデルを複数持っているイメージ)の実装です。

todo-collection.js
var app = app || {};

//Todo一覧表示用コレクション
(function(app) {
    app.TodoCollection = Backbone.Collection.extend({
        url : '/rest-study/todo_lists.json',
        model : app.TodoModel,

        parse : function(response) {
            //コレクションをパース
            console.log("コレクションをパース");
            return response;
        }
    });
})(app);
  • url変数
    fetch関数実行時にアクセスするサーバ側APIのURLを指定します。

  • model変数
    このコレクション内に含むモデルのオブジェクトを指定します。
    ここでは、このコレクションに含まれるモデルは全てapp.TodoModelであることを示しています。

  • parse関数
    fetch関数(親のBackbone.Collectionが持っている)を実行し、正しくサーバからデータが取得できれば、parse関数が実行されます。
    ここでは、そのparse関数をオーバーライドし、ログNo.6を表示しています。
    ※この直前、parse関数が実行される前に、jquery内の処理でログNo5が表示されています。

todo-model.js

TODOの一件を扱う、モデルの実装です。

todo-model.js
var app = app || {};

//Todoデータ1件を表すモデル
(function(app) {
    app.TodoModel = Backbone.Model.extend({
        urlRoot : '/rest-study/todo_lists',
        parse : function(response) {
            //モデルをパース
            console.log("モデルをパース");
            console.log(response);
            return response.TodoList;
        }
    });
})(app);
  • urlRoot変数
    fetch関数実行時にアクセスするサーバ側APIのURLを指定します。

  • parse関数
    fetch関数を実行し、正しくサーバからデータが取得できれば、この関数が実行されます。
    ここでは、ログNo.7を表示しています。この直前、parse関数が実行される前に、jquery内の処理でログNo5が表示されています。

※重要
parse関数内で、return response.TodoListとしていますが、これには下記の意味があります。
TODOリスト全体を取得した際、サーバから返ってくるJsonデータは、下記のフォーマットです。

[
    {
        "TodoList": {
            "id": "1",
            "todo": "TODO 1件目",
            "status": "0"
        }
    },
    {
        "TodoList": {
            "id": "2",
            "todo": "TODO 2件目",
            "status": "0"
        }
    },
    {
        "TodoList": {
            "id": "3",
            "todo": "TODO 3件目",
            "status": "0"
        }
    }
]

データ一件分は、下記です。

"TodoList": {
    "id": "1",
    "todo": "TODO 1件目",
    "status": "0"
}

全データがTodoListというキー名のオブジェクトになっています。
ここでやっているのは、そのオブジェクトの中身、

{
    "id": "1",
    "todo": "TODO 1件目",
    "status": "0"
}

だけをreturnし、後々のデータの扱いの利便性を図るためにやっています。
これがないと、idにアクセスする際、TodoList.id、statusにアクセスする際はTodoList.statusと毎回やる手間をここで省いているわけです。

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/routers/router.js を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-collection-view.js を上記の通り作成。
  • :white_check_mark: app/webroot/js/collections/todo-collection.js を上記の通り作成。
  • :white_check_mark: app/webroot/js/models/todo-model.js を上記の通り作成。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

Lesson3へ!

Lesson 3 一覧画面表示

Lesson2では、MVCの基本的な流れを確認し、データが取得できていることを確認しました。
Lesson3では、その取得したデータを一覧表示してみます。

Lesson完了後のアプリの動作

http://[PublicIP]/rest-study/#todo-lists
にアクセスすると、下図の用にTODOの一覧が表示されます。
まだ表示されるだけです。

lesson3-1.jpg

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
修正 app/webroot/js/views/todo-collection-view.js ビュー(Todoリスト一覧)
追加 app/webroot/js/views/todo-item-view.js ビュー(Todoリスト一覧内の1件表示用)

各ファイルの編集内容とポイント解説

default.ctp

default.ctp
 <title>シンプルTODOアプリ</title>
 </head>
 <body>
+   <!-- コンテンツ -->
+   <div id="content">
+   </div>
+   <!-- TODO一覧表示のテンプレート -->
+   <script type="text/template" id="list-template">
+   <h1>TODOリスト</h1>
+   <hr>
+   <div>
+       <table border="1" width="350px">
+           <tbody id="todo-lists"></tbody>
+       </table>
+   </div>
+   </script>
+
+   <!-- TODO一行分のテンプレート(上のtbody部分に挿入される) -->
+   <script type="text/template" id="item-template">
+   <td style="margin:0px">
+       <span class="todo-edit" style="margin:0px"><%- todo %></span>
+   </td>
+   </script>

    <!-- js(library) -->
    <script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
 @@ -17,6 +37,7 @@
    <!--   collection   -->
    <script src="js/collections/todo-collection.js" type="text/javascript"></script>
    <!--   view   -->
+   <script src="js/views/todo-item-view.js" type="text/javascript"></script>
    <script src="js/views/todo-collection-view.js" type="text/javascript"></script>
    <!--   router   -->
    <script src="js/routers/router.js" type="text/javascript"></script>
  • TODO一覧用のテンプレート(HTML断片)
    • <script type="text/template" id="list-template">から始まる部分
  • その中の一件を表示するためのテンプレート
    • <script type="text/template" id="item-template">から始まる部分 を追加しています。 さらに、1件表示用のビューであるtodo-item-view.jsの読み込みを追加しています。

todo-collection-view.js

todo-collection-view.js
 //Todo一覧表示用ビュー
 (function(app) {
    app.TodoCollectionView = Backbone.View.extend({
+       el : '#content',
+       tagName : 'div',
        todoCollection : {},
        initialize : function() {
-           console.log("Todo一覧表示用ビュー初期化");
-           //コレクションを生成
            this.todoCollection = new app.TodoCollection()
+           this.todoCollection.on('add', this.addOne, this);
+           this.$el.html($('#list-template').html());
            this.render();
        },
        render : function() {
-           console.log("Todo一覧表示用ビュー表示処理");
-           //コレクションをフェッチ
-           console.log("コレクションをフェッチ");
            this.todoCollection.fetch();
            return this;
        },
+
+       addOne : function(todo) {
+           var itemView = new app.TodoItemView({
+               model : todo
+           });
+           $('#todo-lists').append(itemView.render().el);
+       },
    })
 })(app);

削除したのはLesson2で入れたconsole.logなどです。

追加した行は以下。

  • el変数

    Viewが管理するDOM上のエレメントを設定しておく変数です。
    '#content'を設定し、default.ctp内にある<div id="content">要素を指しています。

  • tagName変数

    Viewがel変数で示される要素内にDOMエレメントを追加する際のデフォルトのタグ名を設定します。

  • initialize関数

    • this.todoCollection.on('add', this.addOne, this);

      collectionがデータ取得(fetch関数)の成功時に、collection内に1件データを格納するたびにaddイベントを発火するのですが、そのイベントをキャッチして、addOne関数が実行されるように指定しています。

    • this.$el.html($('#list-template').html());

      el変数で指定したエレメントに、default.ctpの'#list-template'で指定されたテンプレートを描画しています。

  • addOne関数
    app.TodoItemView(1件表示用のビュー)を初期化し、テンプレート内の'#todo-lists'で指定された<tbody>タグ内に、1件表示用のビューの描画結果全体を追加しています。これで、TODOが一件追加表示されています。

todo-item-view.js

Todoリスト一覧内の1件表示用のビューの実装です。

todo-item-view.js
var app = app || {};

//Todo一覧の1件表示用ビュー
(function(app) {
    app.TodoItemView = Backbone.View.extend({
        //DOMに要素追加のタグ名
        tagName : 'tr',

        //テンプレート
        template : _.template($('#item-template').html()),

        initialize : function() {
        },
        render : function() {
            this.$el.html(this.template(this.model.toJSON()));
            return this;
        },
    });
})(app);
  • tagName変数

    一覧表示は<Table>タグで表示するので、1件分は<tr>タグになります。

  • render関数

    • this.todoCollection.on('add', this.addOne, this);

      collectionがデータ取得(fetch関数)の成功時に、collection内に1件データを格納するたびにaddイベントを発火するのですが、そのイベントをキャッチして、addOne関数が実行されるように指定しています。

    • this.$el.html($('#list-template').html());

      el変数で指定したエレメントに、default.ctpの'#list-template'で指定されたテンプレートを描画しています。

  • addOne関数
    app.TodoItemView(1件表示用のビュー)を初期化し、default.ctpの'#todo-lists'で指定されたテンプレートを使用してモデルの内容を表示(DOM要素の生成)しています。

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-collection-view.js を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-item-view.js を上記の通り作成。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

Lesson4へ!

Lesson 4 一件追加

TODOを追加できるようにしていきます。

Lesson完了後のアプリの動作

http://[PublicIP]/rest-study/#todo-lists
にアクセスすると、下図の通り、1件追加用のテキストエリアと追加ボタンが追加されています。
lesson4-1.jpg

「TODO 4件目」と入力し、「追加」ボタンをクリックします。
lesson4-2.jpg

追加されました!
lesson4-3.jpg

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
修正 app/webroot/js/views/todo-collection-view.js ビュー(Todoリスト一覧)

各ファイルの編集内容とポイント解説

default.ctp

default.ctp
    <!-- TODO一覧表示のテンプレート -->
    <script type="text/template" id="list-template">
    <h1>TODOリスト</h1>
+   <textarea style="width:300px;height:50px"id="new-todo" placeholder="Todo?" autofocus></textarea>
+   <input type="button" id="addTodo" value="追加">
    <hr>
    <div>
        <table border="1" width="350px">
 @@ -45,4 +47,4 @@
    <script src="js/app.js" type="text/javascript"></script>

 </body>

下記を追加しています。

  • TODO入力用のテキストエリア
  • 追加ボタン

todo-collection-view.js

todo-collection-view.js
        tagName : 'div',
        todoCollection : {},
        initialize : function() {
            this.todoCollection.on('add', this.addOne, this);
            this.$el.html($('#list-template').html());
+           this.newTodo = this.$('#new-todo');
            this.render();
        },
+       events : {
+           'click #addTodo' : 'onCreateTodo',
+       },
        render : function() {
            this.todoCollection.fetch();
            return this;
        },
+       onCreateTodo : function(e) {
+           this.todoCollection.create(this.newAttributes(), {
+               wait : true
+           });
+           this.newTodo.val('');
+           this.todoCollection.fetch();
+       },
        addOne : function(todo) {
            var itemView = new app.TodoItemView({
                model : todo
            });
            $('#todo-lists').append(itemView.render().el);
        },
+       newAttributes : function() {
+           return {
+               todo : this.newTodo.val().trim(),
+               status : 0
+           }
+       }
    })
 })(app);
  • initialize関数

    • this.newTodo = this.$('#new-todo');

      $('#new-todo')はテキストエリアです。後で使用するためinisialize処理内で取得して変数に格納しています。

    • events変数

      • 'click #addTodo' : 'onCreateTodo' Viewのevents{}には、DOMイベントとそのハンドラ関数の対応付けを行います。 backboneのViewがControllerの役割を兼ねているのはこの部分ですね。 ここでは、追加ボタンのClickイベントにonCreateTodo関数を割り当てています。
    • onCreateTodo関数
      events{}で割り当てた追加ボタンの処理の実装です。

      • collectionのcreate関数を実行し、データを一件追加します。これで、サーバの一件追加APIを呼び出します(ここでは、エラー処理等は入れず、単に終了を待っています)。追加する内容は、newAttributes関数で作成しています。
      • 追加が成功したら、collectionをfetchし、最新のTODO一覧を取得しなおしています。
    • newAttributes関数

      • todo : 入力値
      • status : 0(未完了)

      で新しいModelを生成してreturnしています。

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-collection-view.js を上記の通り修正。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

Lesson5へ!

Lesson 5 チェックボックス更新

チェックボックスを追加します。
TODOが完了したら、チェックを付ける機能です。

Lesson完了後のアプリの動作

http://[PublicIP]/rest-study/#todo-lists
にアクセスすると、下図の通り、TODO表示の左側にチェックボックスが追加されています。
図では、1件目がチェック付きの状態になっています。2件目にチェックを付けたらすぐサーバに反映されます。
リロードしてもチェックが残っていることが確認できます。

lesson5-1.jpg

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
修正 app/webroot/js/models/todo-model.js モデル(Todo一件)
修正 app/webroot/js/views/todo-item-view.js ビュー(Todoリスト一覧内の1件表示用)

各ファイルの編集内容とポイント解説

default.ctp

default.ctp
    <!-- TODO一行分のテンプレート(上のtbody部分に挿入される) -->
    <script type="text/template" id="item-template">
+   <td><input type="checkbox" class="toggle" <%- status === '1' ? 'checked' : '' %>></td>
    <td style="margin:0px">
        <span class="todo-edit" style="margin:0px"><%- todo %></span>
    </td>

チェックボックスを表示するためのHTML断片を追加しています。
<%- status === '1' ? 'checked' : '' %>>
この部分、statusが1の場合はchecked属性を付加する、という処理を3項演算子で書いています。

todo-model.js

todo-model.js
            console.log("モデルをパース");
            console.log(response);
            return response.TodoList;
+       },
+       toggle : function() {
+           this.set('status', this.get("status") === '1' ? '0' : '1');
+           this.save();
        }
    });
 })(app);

toggle関数を追加しています。
model自身のstatusが、

  • 0なら1に
  • 1なら0に

と変換する処理を3項演算子で書いています。
その後、自身のsave()関数でサーバ側APIを呼び出して保存しています。
viewの処理(チェックボックスをクリックされた時のハンドラ関数)内から呼び出されます。

todo-item-view.js

todo-item-view.js
        //テンプレート
        template : _.template($('#item-template').html()),

+       //DOMイベントハンドラ設定
+       events : {
+           //チェックボックスクリック時
+           'click .toggle' : 'onStatusToggleClick',
+       },
+
        initialize : function() {
        },
        render : function() {
            this.$el.html(this.template(this.model.toJSON()));
            return this;
        },
+       onStatusToggleClick : function(e) {
+           this.model.toggle();
+       },
    });
 })(app);

events変数
チェックボックスクリック時のハンドラ関数onStatusToggleClickを設定しています。

onStatusToggleClick関数
チェックボックスクリック時の実装です。
modelのtoggle関数を実行し、ステータスを変更、保存しています。

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/models/todo-model.js を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-item-view.js を上記の通り修正。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

Lesson6へ!

Lesson 6 一件削除

不要なTODOを削除する機能を付けます。

Lesson完了後のアプリの動作

http://[PublicIP]/rest-study/#todo-lists
にアクセスすると、下図の通り、TODO表示の右側に削除用のリンクが追加されています。

lesson6-1.jpg

クリックすると、下図の通りTODO2件目が削除されました。
lesson6-2.jpg

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
修正 app/webroot/js/views/todo-item-view.js ビュー(Todoリスト一覧内の1件表示用)

各ファイルの編集内容とポイント解説

default.ctp

default.ctp
    <td style="margin:0px">
        <span class="todo-edit" style="margin:0px"><%- todo %></span>
    </td>
+   <td>
+       <a class="remove-link" href="#">削除</a>
+   </td>
    </script>

    <!-- js(library) -->

削除用のリンクを表示するためのHTML断片を追加しています。

todo-item-view.js

todo-item-view.js
        events : {
            //チェックボックスクリック時
            'click .toggle' : 'onStatusToggleClick',
+           //削除ボタンクリック時
+           'click .remove-link' : 'onRemoveClick',
        },

        initialize : function() {
+           this.listenTo(this.model, 'destroy', this.remove);
        },
        render : function() {
            this.$el.html(this.template(this.model.toJSON()));
            return this;
        },
        onStatusToggleClick : function(e) {
            this.model.toggle();
        },
+       onRemoveClick : function(e) {
+           this.model.destroy({
+               wait : true
+           });
+       },
    });
 })(app);

events変数
削除リンククリック時のハンドラ関数onRemoveClickを設定しています。

initialize関数
削除リンククリック時にmodelを削除(destroy)しますが、実際に削除された際にdestroyイベントが発火されます。
そのイベントのハンドラ関数this.removeを設定しています。
remove関数はview自体を、そのviewのel変数で示されるDOM要素を全て破棄、および関連付けているイベントを全て破棄します。
これで画面上のTODO一件が削除されます。

onRemoveClick関数
eventsで設定した関数の実装です。
modelを削除するdestroy関数を実行しています。
この後、実際に削除が完了すると、destoroyイベントが発生し、initialize関数で設定したとおりviewが削除されます。

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-item-view.js を上記の通り修正。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

Lesson7へ!

Lesson 7 更新画面を表示

TODOの内容を編集するための「詳細画面」に遷移する機能を実装します。

Lesson完了後のアプリの動作

http://[PublicIP]/rest-study/#todo-lists
にアクセスすると、下図の通り、削除用のリンクの右側に詳細リンクが追加されています。

lesson7-1.jpg

TODO3件目の詳細リンクをクリックすると、下図の通り詳細画面に遷移します。
テキストエリアにTODOの内容が表示されています。編集できますが、まだキャンセルボタンしかありません。

lesson7-2.jpg

キャンセルボタンをクリックするとTODO一覧に戻ります。

lesson7-3.jpg

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
修正 app/webroot/js/routers/router.js ルータ(Controller)
追加 app/webroot/js/views/todo-detail-view.js ビュー(Todo1件の詳細画面表示用)

各ファイルの編集内容とポイント解説

default.ctp

default.ctp
 </head>
 <body>
    <!-- コンテンツ -->
-   <div id="content">
-   </div>
+   <div id="main"></div>
    <!-- TODO一覧表示のテンプレート -->
    <script type="text/template" id="list-template">
    <h1>TODOリスト</h1>
 @@ -29,8 +28,17 @@
    </td>
    <td>
        <a class="remove-link" href="#">削除</a>
+       <a class="detail-link" href="#todo-lists/<%- id %>">詳細</a>
    </td>
    </script>
+   <!-- 詳細画面 -->
+   <script type="text/template" id="detail-template">
+   <h2>Todo #<%- id %></h2>
+   <div>
+   <textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
+   <input type="button" id="updateCancel" value="キャンセル"></input>
+   </div>
+   </script>

    <!-- js(library) -->
    <script src="js/lib/jquery-2.1.3.min.js" type="text/javascript"></script>
 @@ -44,6 +52,7 @@
    <script src="js/collections/todo-collection.js" type="text/javascript"></script>
    <!--   view   -->
    <script src="js/views/todo-item-view.js" type="text/javascript"></script>
+   <script src="js/views/todo-detail-view.js" type="text/javascript"></script>
    <script src="js/views/todo-collection-view.js" type="text/javascript"></script>
    <!--   router   -->
    <script src="js/routers/router.js" type="text/javascript"></script>
  • <div id="content">を、<div id="main">に変更

    これは、後のrouter.jsの説明で述べますが、画面遷移時に<div id="content">要素を<div id="main">の下に動的に追加するためです。

  • 詳細画面へのリンクを追加

    リンク先をhref="#todo-lists/<%- id %>"としています。
    これで、Lesson1で設定したルーティングに一致する #todo-lists/:idへのリンクとなります。

  • 詳細画面のテンプレートを追加

    タイトル(id)の表示、テキストエリアとキャンセルボタンを追加しています。

  • 詳細表示用ビューの読み込み追加

    詳細表示用のビューであるtodo-detail-view.jsの読み込みを追加しています。

router.js

router.js
+
+       currentView : false,
+
        todoLists : function() {
            //Todo一覧表示用ビューにルーティング
-           console.log("Todo一覧表示用ビューにルーティング");
-           new app.TodoCollectionView();
+           this.removeCurrentView();
+           this.nextView(app.TodoCollectionView);
        },

        todoDetail : function(id) {
-           alert('id = ' + id + ' のTODO詳細表示');
+           this.removeCurrentView();
+           this.nextView(app.TodoDetailView, id);
        },
+
+       nextView : function(View, option) {
+           if (document.getElementById('#content') === null) {
+               $('#main').append('<div id="content"/>');
+           }
+           this.currentView = new View(option);
+       },
+       removeCurrentView : function() {
+           if (this.currentView) {
+               this.currentView.remove();
+           }
+       }
+
    });
 })(app);

Lesson6までは画面が一つだけでしたが、詳細画面を追加しました。
その為、Viewの生成処理を変更し、さらにViewを破棄する処理を追加しています。

不要なconsole.logとalertは削除しています。

  • currentView変数

    現在表示しているViewのインスタンスを格納するための変数です。

  • todoLists関数

    修正前は単に、new app.TodoCollectionView();とTODO一覧用Viewを生成するだけでしたが、

    • 現在表示中のビュー(currentView変数が指しているビュー)を削除(removeCurrentView関数)
    • TODO一覧用Viewを生成する(nextView関数)

    の2つの処理になっています。

  • todoDetail関数

    todoLists関数と同じく、removeCurrentView関数とnextView関数を実行しています。
    nextView関数には、

    • app.TodoDetailView
    • id

    の2つを引数として渡しています。

  • nextView関数

    ここで、default.ctpの説明で少し触れた、content要素の動的追加を行っています。
    content要素がなければ、main要素の下に追加するという処理をしています。
    ビューの削除時にはcontent要素ごと削除されるので、content要素を追加してからビューを生成するわけです。

    詳細画面の場合、view引数=app.TodoDetailView, option引数=idとなっているので、
    app.TodoDetailViewのinitialize実行時にidが渡されることになります。

  • removeCurrentView関数

    currentView変数が示すビューを削除しています。content要素ごと削除されます。

todo-detail-view.js

todo-detail-view.js
var app = app || {};

//詳細ビュー
(function(app) {
    app.TodoDetailView = Backbone.View.extend({
        el : '#content',

        //テンプレート
        template : _.template($('#detail-template').html()),

        //DOMイベントハンドラ設定
        events : {
            //キャンセルボタンクリック時
            'click #updateCancel' : 'onCancelClick',
        },

        //初期化
        initialize : function(id) {
            //Routerからidを受け取ってモデル生成
            this.model = new app.TodoModel({
                id : id
            });
            //モデルのサーバからのデータ取得完了時、描画を行う
            this.listenTo(this.model, 'sync', this.render);
            //モデル破棄(destroy)イベント発生時、Viewを削除
            this.listenTo(this.model, 'destroy', this.remove);
            //サーバからデータ取得
            this.model.fetch({
                wait : true
            });
        },

        //描画
        render : function() {
            //テンプレートを使用し、モデルを描画する
            this.$el.html(this.template(this.model.toJSON()));
            return this;
        },

        //キャンセルボタンクリックのイベントハンドラ
        onCancelClick : function() {
            Backbone.history.navigate('#todo-lists', true);
        },

    });
})(app);

TODOの詳細画面用のビューです。

template, eventsについては、これまでと同様の動きですので割愛します。

  • initialize関数

    routerにより当ビュー生成時にidが渡されましたが、ここでそのidのみを持つモデルを作成し、モデルのfetch関数を実行することでサーバのAPIを実行し、最新の内容を取得しています。

    syncイベント(サーバから最新情報取得後発火)にはrender関数、
    destroyイベントにはビュー自身のremove関数を指定しています。

  • render関数

    initialize関数で設定したとおり、実行したfetchの完了による発火されるsyncイベントを契機として呼び出されます。
    テンプレートを描画しています。

  • onCancelClick関数

    キャンセルボタン押下時の実装です。
    Backbone.history.navigate関数により、'#todo-lists'にURLを変更しています。
    2番目の引数trueにより、同時にそのURLへ遷移(=routerを動作させる)させています。
    これでrouterが反応してTODO一覧画面に戻ります。

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/routers/router.js を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-detail-view.js を上記の通り作成。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット

では、最後のLesson8へ!

Lesson 8 更新処理

詳細画面に更新ボタンを追加し、更新できるようにします。

Lesson完了後のアプリの動作

TODO一覧画面で、"TODO 3 件目"の詳細リンクをクリック後の画面です。
更新ボタンが追加されています。
内容を"3/27 19:00 第3回勉強会に参加"に変えて更新ボタンをクリックしてみます。
lesson8-1.jpg

TODOの内容が更新され、TODO一覧画面に遷移後表示されました。
今回はこれで完成です!

lesson8-2.jpg

編集するファイル一覧

編集 file 役割
修正 app/View/Layouts/default.ctp HTMLテンプレート
修正 app/webroot/js/views/todo-detail-view.js ビュー(Todo1件の詳細画面表示用)

各ファイルの編集内容とポイント解説

default.ctp

default.ctp
    <h2>Todo #<%- id %></h2>
    <div>
    <textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
+   <input type="button" id="updateTodo" value="更新"></input>
    <input type="button" id="updateCancel" value="キャンセル"></input>
    </div>
    </script>

更新リンクの追加のみです。

todo-detail-view.js

todo-detail-view.js
        //DOMイベントハンドラ設定
        events : {
+           //更新ボタンクリック時
+           'click #updateTodo' : 'onUpdateClick',
            //キャンセルボタンクリック時
            'click #updateCancel' : 'onCancelClick',
        },

〜中略〜

        render : function() {
            //テンプレートを使用し、モデルを描画する
            this.$el.html(this.template(this.model.toJSON()));
+           //入力欄への参照を取得しておく
+           this.$textBox = this.$('#edit-todo');
            return this;
        },

+       //更新ボタンクリックのイベントハンドラ
+       onUpdateClick : function() {
+           //テキストボックスから文字を取得
+           var todoString = this.$textBox.val();
+           this.model.save({
+               todo : todoString
+           }, {
+               wait : true,
+               silent : true,
+               success : function() {
+                   Backbone.history.navigate('#todo-lists', true);
+               }
+           });
+       },
+
        //キャンセルボタンクリックのイベントハンドラ
        onCancelClick : function() {
            Backbone.history.navigate('#todo-lists', true);

TODOの詳細画面用のビューです。

  • onUpdateClick関数

    モデルを入力値で更新(sava関数)し、終了後キャンセルボタンと同じくBackbone.history.navigateを使用してTODO一覧画面に戻っています。

このsave処理ですが、

save処理
    this.model.save({
        todo : todoString
    }, {
        wait : true,
        silent : true,
    });
    Backbone.history.navigate('#todo-lists', true);

でも動きます。これまでの書き方はこうでしたね。
今回は下記のようにsuccess関数を指定し、その中で画面遷移するようにしています。
本来、サーバとのアクセス時には何らかのエラー処理が必要になります。
成功時にはsuccess関数、エラー時にはerror関数を指定しておけばそれが実行されるので、成功時の処理、エラー時の処理を整理して書くことが出来ます。
今回は例としてここだけsuccess関数を実装してみました。

save処理
    this.model.save({
        todo : todoString
    }, {
        wait : true,
        silent : true,
        success : function() { // ***** ココ *****
            Backbone.history.navigate('#todo-lists', true);
        }
    });
},

実装

では、実装しましょう!

  • :white_check_mark: app/View/Layouts/default.ctp を上記の通り修正。
  • :white_check_mark: app/webroot/js/views/todo-detail-view.js を上記の通り修正。
  • :white_check_mark: 動作確認!
  • :white_check_mark: Gitにコミット
  • :white_check_mark: 最後に、前回に倣ってgit pushしてGitHubで確認しておきましょう。

完成です!お疲れ様でした!

コメント/フィードバックお待ちしております。

参加者の方も、そうでない方もお気づきの点があればお願い致します。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away