はじめに
本投稿は、2015/3/26に行われたAWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~【第3回】 - connpassの内容についてまとめた資料です。
ご参加頂いた方へ。
有難うございました!次回もぜひよろしくお願いします。
今後の予定⇢AWS上で構築するRESTfulアプリ勉強会~Web開発ワークショップ~ - connpass
今回は、Marionette.jsを使用します。
Backboneだけでは面倒な部分、煩雑になる部分などを解消していく過程を体験していきましょう!
Marionetteの概要
Marionette.jsとは?
Backboneはその名の通り、クライアントアプリケーションでMVCモデルを構築する際の「背骨」を提供します。しかし、まさに背骨のみであり、実際のアプリケーション構築ではさまざまなコードをプログラマが書く必要があり、「毎回同じようなコードを書く」という状況が往々にして発生します。
前回のプログラムで言うと、
- Viewの切り替えをしている部分
- ネストしたViewの管理
- サーバからのデータ取得からViewのRenderまでの一連のコード
などです。Marionetteを使用すると、かなりの部分を肩代わりしてくれます。
また、アプリケーションの規模が大きくなってくると、BackboneのみではViewの管理が大変になってきます。全体のレイアウトを定義するビュー、イベントを定義するビュー、コレクションを表示するビュー、モデルを表示するビュー、etc....。
これらも、Marionetteが提供する、役割に応じた複数のViewやRegionといった機能を利用することでViewの構造が把握しやすくなり、可読性、保守性の向上につながります。
他にも、ui
などの便利な機能、今回は触れませんが、triggers
やBehaviors
など、有益な機能が盛り込まれています。
Backboneにはさまざまなプラグインがありますが、まず最初に導入を検討すべきなのがこのMarionetteといえるでしょう。
Marionetteオブジェクトをざっくり紹介
Marionetteは、MVCでいう、コントローラとビューに対しての機能を持ちます。
Backboneのオブジェクトが担っていた機能をMarionetteが提供するオブジェクトに置き換えていくことになります。今回使用するMarionetteのオブジェクトは下記のとおりです。各オブジェクトの機能詳細は後ほど述べます。
まずはこれだけある、ということで。
コントローラ系
Marionetteオブジェクト | 概要 |
---|---|
Marionette.Application | Marionetteを使用したアプリケーションのスタートポイントとなります |
Marionette.AppRouter | ルーティングを行います。 |
Marionette.Controller | コントローラです。 |
ビュー系
Marionetteオブジェクト | 概要 |
---|---|
Marionette.Application | リージョン定義機能を持ちます。 |
Marionette.Region | 画面に定義する領域「リージョン」を表すオブジェクトです。 |
Marionette.LayoutView | 複数のビューのレイアウトを定義することに適したビューです。 |
Marionette.CollectionView | Collectionの表示に適したビューです。 |
Marionette.CompositeView | Collectionの表示に適したビューです。 |
Marionette.ItemView | Modelの表示に適したビューです。 |
各ビューのリージョン機能、テンプレート機能の有無。
View | リージョン機能 | テンプレート機能 |
---|---|---|
Marionette.Application | 有り | 無し |
Marionette.LayoutView | 有り | 有り |
Marionette.CollectionView | 無し | 無し |
Marionette.CompositeView | 無し | 有り |
Marionette.ItemView | 無し | 有り |
Backboneオブジェクトとの関係
Backboneのオブジェクトと、Marionetteのオブジェクトとの対応関係は以下のようになります。
Backbone | Marionette |
---|
-
|Marionette.Application|
Backbone.Router |Marionette.AppRouter
Marionette.Controller|
Backbone.View |Marionette.Region
Marionette.LayoutView
Marionette.CollectionView
Marionette.CompositeView
Marionette.ItemView
|
今回の内容
前回作成したプログラムを、Marionette.jsの各オブジェクトを使用して書き換えていきます。
画面は何も変わりません。内部の実装だけが変わります。
ではまず、準備から!
事前準備
SSHでログイン
sshログインから始めましょう!
-
ssh -i [秘密鍵のパス] study@[サーバのPublicIP]
ディレクトリを移動しておきます。
-
cd /var/www/study/rest-study
では、はじめましょう!
gitのブランチを整えておく
前回は、vol/02
ブランチで作業をしました。前回のマニュアルでは、vol/02
ブランチをpushしてGitHubで確認まで、としていましたので、まず前回までの内容をmaster
ブランチにマージします。
前回が作業途中の方や、今回から参加の方は、新たにディレクトリを作成します。
今回から参加される方、第1回の内容は終えていることが条件ですのでご了承ください。
今回分から始められる方、前回が途中の方、終わっているかたでやり方が違うのでご注意ください。
やりたいことは、
- masterブランチを前回の終了状態にする
- masterブランチを元に、今回の作業用である、「vol/03」ブランチを作成する
前回が作業途中の方
とにかくコミットしましょう!
途中でもとにかく!
次は全員同じ。 fork元をリモートリポジトリとして追加する
第一回勉強会でforkした元のリポジトリをリモートリポジトリとして追加します。
git remote
とコマンドを打って、
origin
upstream
と表示される場合はこの作業は終わっています。次へ行きましょう!
upstream
が表示されない人は次のコマンド。
git remote add upstream https://github.com/suzukishouten-study/rest-study.git
これでOKです!
次も全員同じ。 upstreamリポジトリから最新を取得する
git fetch upstream
これでOK!
次は、
- 前回の内容を完了した方
- 前回の内容を途中までやった方と今回から参加の方
で少し違うのでご注意!
前回の内容を完了した方
masterブランチを前回分を終了した状態にします。
前回を完了した方は、vol/02
ブランチをマージです。
git checkout master
でmasterブランチをチェックアウト後、
git merge vol/02
でマージ。これでOK!
前回の内容を途中までやった方と今回から参加の方
masterブランチを前回分を終了した状態にします。
前回の内容を途中までやった方と今回から参加の方は、upstreamリポジトリのvol/02-finish
ブランチをマージです。
git checkout master
でmasterブランチをチェックアウト後、
git reset --hard upstream/vol/02-finish
で強制的にマージ。これでOK!
今回の作業用のbranchを作成
全員同じ手順です。
今回の作業用ブランチとして、vol/03
ブランチを作り、チェックアウトします。
master
ブランチから分岐してvol/03
ブランチを作成するので、まずmaster
ブランチにいることを確認して下さい。
これまでの作業で、既にmasterブランチにいるはずですが。
-
git branch
でmaster
にいることを確認 -
master
にいなかった場合は、git checkout master
でmaster
をチェックアウト
以下コマンドでvol/03
ブランチを作成します。
-
git branch vol/03
でブランチを作って -
git checkout vol/03
でそのブランチに変更
確認
念のため確認します。
-
git log
でコミットログを確認してみましょう。
コミットログが出力されます。
前回の最後のコミットが表示されていればOKです。
これでブランチの準備は整いました。
git branch -a
と、全ブランチを表示すると、vol/03-finish
ブランチが見えると思いますが、これは今回の内容が全て終えた状態のソースが入っています。このソースは、下記URLから各Lesson毎のコミットが確認できますので参考にしてください。
Commits · suzukishouten-study/rest-study
Marionetteライブラリダウンロード
Marionetteの公式サイトMarionette.js – The Backbone Frameworkから取得します。
ダウンロードURLは下記です。サーバにSSHでログインし、wgetコマンドで取得します。
このあたりの手順は前回の資料にも記載がありますので参考にしてください。
- /var/www/study/rest-study/app/webroot/js/libにダウンロードします。
cd /var/www/study/rest-study/app/webroot/js/lib
wget http://marionettejs.com/downloads/backbone.marionette.min.js
これで準備完了です!
では、Marionetteの各オブジェクトがどのように機能するのか、詳しく見て行きましょう。
まずはLesson1、「エントリポイント・ルーティング・コントローラ」と題し、前回作成したプログラムに、
- Marionette.Application
- Marionette.AppRouter
- Marionette.Controller
の3つを適用していきます。
Lesson 1 エントリポイント・ルーティング・コントローラ
Marionetteを使用して一番恩恵を受けるのはビュー部分なのですが、その前にまずエントリポイント・ルーティング・コントローラのあたりの変更点を抑えておくのがLesson1です。
まだMarionetteが本領を発揮するところではありません。
やっていることは下記のとおりです。
- エントリポイントの変更
- ルータとコントローラの分離
下記のMarionetteオブジェクトを使って、前回のプログラムを修正していきます。
- Marionette.Application
- Marionette.AppRouter
- Marionette.Controller
編集するファイル一覧
編集 | file | 役割 |
---|---|---|
修正 | app/View/Layouts/default.ctp | HTMLテンプレート |
追加 | app/webroot/js/main.js | アプリケーションの開始ポイント |
修正 | app/webroot/js/app.js | アプリケーション(Marionettte.Application) |
修正 | app/webroot/js/routers/router.js | ルータ(Marionette.AppRouter) |
追加 | app/webroot/js/routers/controller.js | コントローラ |
default.ctp
まずはこのファイルです。
<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>
+ <script src="js/lib/backbone.marionette.min.js" type="text/javascript"></script>
<!-- js(application) -->
〜中略〜
<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>
+ <!-- controller -->
+ <script src="js/routers/controller.js" type="text/javascript"></script>
<!-- router -->
<script src="js/routers/router.js" type="text/javascript"></script>
- <!-- entry point -->
+ <!-- application -->
<script src="js/app.js" type="text/javascript"></script>
+ <!-- entry point -->
+ <script src="js/main.js" type="text/javascript"></script>
</body>
</html>
- 追加したjsファイルの読み込みの追加です。
-
js/lib/backbone.marionette.min.js
- Marionette本体。 -
js/routers/controller.js
- Marionetteにより提供される、コントローラの実装。あとで作成します。 -
js/app.js
- Marionetteにより提供される、Applicationオブジェクトの実装。- 修正前は、これがエントリポイントでしたが、修正後は次のmain.jsがエントリポイントとなります。
-
js/main.js
- エントリポイントです。あとで作成します。
-
main.js
前回は、app.js
がエントリポイントでした。今回の修正で、ファイルを2つにわけます。
- main.js - エントリポイントとなります。
- app.js -
Marionette.Application
を継承したオブジェクトの実装となります。
var app = app || {};
//開始
(function(app) {
app.application = new app.Application();
app.application.start();
})(app);
app.Application(app.jsで定義)をインスタンス化し、start
メソッドを実行するのみです。
app.js
前回app.js
でやっていたことは下記の2つでした。
- routerをインスタンス化
-
Backbone.history.start()
を実行して「hashchangeイベント」の監視を開始
var app = app || {};
//開始
(function(app) {
var todoRouter = new app.TodoRouter();
Backbone.history.start();
})(app);
Marionetteを使用する場合、これらの処理はMarionette.Application
を継承したオブジェクト内で実行します。
ですので、このapp.js
内ではオブジェクトの定義のみ行い、インスタンス化及び実行はmain.js
から、となるわけです。
前述のmain.js
(今回のエントリポイントとなるファイル)内で当オブジェクトがインスタンス化されて実行されます。
//開始
(function(app) {
- var todoRouter = new app.TodoRouter();
- Backbone.history.start();
+ app.Application = Backbone.Marionette.Application.extend({
+ initialize : function(){
+ new app.TodoRouter();
+ },
+
+ onStart : function(){
+ Backbone.history.start();
+ },
+ });
})(app);
やっていることは前回と変わりません。
- インスタンス化 -
initialize
が実行される。- ここでrouterをインスタンス化。
- start関数実行 -
onStart
が実行される。- ここで
hashchange
イベントの監視開始。
- ここで
router.js
routerは、継承元がMarionette.AppRouter
に変わります。
router内ではルーティングの設定のみを書き、ルーティング後実行される関数の実装はcontroller内に書くようになります。
//router
(function(app) {
- app.TodoRouter = Backbone.Router.extend({
- routes : {
+ app.TodoRouter = Backbone.Marionette.AppRouter.extend({
+ //コントローラをインスタンス化
+ controller: new app.TodoController(),
+ //ルーティング設定
+ appRoutes : {
'' : 'todoLists',
'todo-lists' : 'todoLists',
'todo-lists/:id' : 'todoDetail'
},
-
- currentView : false,
-
- todoLists : function() {
- //Todo一覧表示用ビューにルーティング
- this.removeCurrentView();
- this.nextView(app.TodoCollectionView);
- },
-
- todoDetail : function(id) {
- 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);
- ルーティング後に実行される関数の実装は全てコントローラに移動するため、routerからは削除。
-
controller: new app.TodoController()
- コントローラをここでインスタンス化しています。
controller.js
ルーティング後の関数の実装をここに移動しています。
内容はそのままです。
var app = app || {};
//controller
(function(app) {
app.TodoController = Backbone.Marionette.Controller.extend({
currentView : false,
todoLists : function() {
//Todo一覧表示用ビューにルーティング
this.removeCurrentView();
this.nextView(app.TodoCollectionView);
},
todoDetail : function(id) {
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);
実装
では、実装しましょう!
-
app/View/Layouts/default.ctp
を上記の通り修正。 -
app/webroot/js/main.js
を上記の通り追加。 -
app/webroot/js/app.js
を上記の通り修正。 -
app/webroot/js/routers/router.js
を上記の通り修正。 -
app/webroot/js/routers/controller.js
を上記の通り作成。 - 動作確認!
- Gitにコミット
完成ソース(GitHub)へのリンク
Lesson2へ!
Marionetteが本領を発揮するViewの部分を扱います!
Lesson 2 TODO一覧画面にMarionetteのビューを適用
Marionetteが本領発揮する部分、Viewの実装です。
まずはTODO一覧画面を修正します。
前述のとおりMarionetteが提供するViewは4種類ありますが、ここではそれらのうち下記を適用していきます。
- LayoutView
- CompositeView
- ItemView
また、Marionette.Application
とLayoutView
が持つリージョン機能も使用します。
さて、ここで各ビューの役割を詳しく見て行きましょう。
リージョンとビューの関係
ビュー
「ビュー」は、ある特定のデータを表示する最小単位、という認識でよいでしょう。
前回のプログラムだと、
- TODO1件(Model1件)を表示するビュー
- TODO一覧を表示するビュー
がそうです。それぞれ、Model1件、Collection一つを司るビューです。
図にするとこうです。
リージョン
「リージョン」とは、ビューを表示する領域です。
リージョンの機能により、表示するビューを差し替える(画面遷移)ことが可能になります。
前回はViewの破棄を自前でやっていましたが、リージョンの機能によって、これをMarionetteに任せることが出来ます。
今回は、TODO一覧ビューとTODO詳細ビューを切り替える際にこの機能が使われます。
切り替えについてはLesson3で見ていきます。
切り替えイメージ
今回のTODOリストアプリケーションではまだやりませんが、表示する対象が多くなってくると、複数のリージョンを用意して、各役割のビューを表示するようにします。
複数リージョンの利用イメージ
リージョン機能は、Marionette.ApplicationオブジェクトおよびMarionette.LayoutView内に生成が可能です。
典型的なパターンとしては、下記のようになります。
- Marionette.Applicationが、アプリケーションの一番外側の土台となるリージョンを生成
- そのリージョン内にLayoutViewを配置
- そのLayoutView内にデータやボタン等を表示するビューを表示
- またはLayoutView内にさらにリージョンを生成
- そのLayoutView内にデータやボタン等を表示するビューを表示
- またはLayoutView内にさらにリージョンを生成
- そのリージョン内にLayoutViewを配置
という入れ子構造になります。
リージョンに配置するViewは常にLayoutViewを使うように書いてますが、リージョンを使用する必要のないシンプルな画面であれば、LayoutViewではなくCompositeViewやItemViewを直接リージョンに配置するほうがよいでしょう。この辺はケースバイケースです。
具体的には下記のようなイメージになります。
- Marionette.Applicationオブジェクトにより、下記リージョンを管理。
- ヘッダ表示リージョン
- ヘッダー表示ビューをはめ込んで表示する
- コンテンツ表示リージョン
- 内側に表示するビューを切り替える(Xコンテンツ表示LayoutView/Yコンテンツ表示LayoutView)
- フッタ表示リージョン
- フッタ表示ビューをはめ込んで表示する。
- ヘッダ表示リージョン
- Xコンテンツ表示LayoutView
- メニューボタン表示リージョン
- メニューボタン表示LayoutViewをはめ込んで表示する
- 一覧データ表示リージョン
- 一覧データ表示LayoutViewをはめ込んで表示する
- ページ切り替えボタン表示リージョン
- ページ切り替えボタン表示LayoutViewをはめ込んで表示する
- メニューボタン表示リージョン
〜以下同様〜
ポイント
下記Marionetteの各Viewの役割を理解することがポイントです。
- Region
- LayoutView
- CompositeView
- ItemView
下図の通り、上記の順番でネストした構造になっています。
以上踏まえて、下記解説を読みつつプログラムを修正していきましょう!
編集するファイル一覧
編集 | file | 役割 |
---|---|---|
修正 | app/View/Layouts/default.ctp | HTMLテンプレート |
修正 | app/webroot/js/app.js | アプリケーション(Marionettte.Application) |
修正 | app/webroot/js/routers/controller.js | コントローラ |
追加 | app/webroot/js/views/todo-layout-view.js | ビュー(LayoutView:Todoリストのレイアウト用) |
追加 | app/webroot/js/views/todo-composite-view.js | ビュー(CompositeView:Todoリスト一覧) |
修正 | app/webroot/js/views/todo-item-view.js | ビュー(ItemView:Todoリスト一覧内の1件表示用) |
削除 | app/webroot/js/views/todo-collection-view.js | ビュー(Todoリスト一覧) |
default.ctp
LayoutView用のテンプレートがひとつ増えているところがポイントです。
<body>
<!-- コンテンツ -->
<div id="main"></div>
- <!-- TODO一覧表示のテンプレート -->
- <script type="text/template" id="list-template">
+
+ <!-- TODO一覧表示のレイアウトテンプレート -->
+ <script type="text/template" id="todo-layout-template">
<h1>TODOリスト</h1>
+ <div id="todo-lists"></div>
+ </script>
+
+ <!-- TODO一覧表示のテンプレート -->
+ <script type="text/template" id="todo-composite-template">
<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">
- <tbody id="todo-lists"></tbody>
+ <tbody></tbody>
</table>
</div>
</script>
<!-- TODO一行分のテンプレート(上のtbody部分に挿入される) -->
- <script type="text/template" id="item-template">
+ <script type="text/template" id="todo-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>
〜中略〜
<a class="detail-link" href="#todo-lists/<%- id %>">詳細</a>
</td>
</script>
<!-- 詳細画面 -->
<script type="text/template" id="detail-template">
<h2>Todo #<%- id %></h2>
〜中略〜
<!-- 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>
+ <script src="js/views/todo-composite-view.js" type="text/javascript"></script>
+ <script src="js/views/todo-layout-view.js" type="text/javascript"></script>
<!-- controller -->
<script src="js/routers/controller.js" type="text/javascript"></script>
<!-- router -->
-
TODO一覧表示のテンプレートを、LayoutViewで使用する部分とCompositeViewを使用する部分に分割しています。
- TODO一覧表示のテンプレートは全部で、レイアウト用、一覧用、1件表示用になります。それぞれIDは下記のように変更しています。
- レイアウト用 ->
todo-layout-template
- 一覧用 ->
todo-composite-template
- 1件用 ->
todo-item-template
- レイアウト用 ->
- TODO一覧表示のテンプレートは全部で、レイアウト用、一覧用、1件表示用になります。それぞれIDは下記のように変更しています。
-
レイアウト用のビューに、リージョンとする部分のタグを追加し、idを
todo-lists
としています(ここに一覧表示用のビューが読み込まれます)。 -
一覧表示用のビュー(
todo-collection-view.js
)はtodo-composite-view.js
に変更しています。 -
レイアウト用のビュー、
js/views/todo-layout-view.js
を追加しています。
app.js
onStart : function(){
Backbone.history.start();
},
+
+ regions : {
+ mainRegion : '#main'
+ }
+
});
-
Marionette.Applicaton
が持つ、リージョン機能の設定を追加しています。この指定で、default.ctp
の<div id="main"></div>
のタグ内がリージョンとして扱えるようになります。
controller.js
Marionetteのリージョンの機能を使用してビューの描画や破棄を行っているので、そのあたりを自前でやっていた部分のコードが削減されています。
(function(app) {
app.TodoController = Backbone.Marionette.Controller.extend({
- currentView : false,
-
todoLists : function() {
- //Todo一覧表示用ビューにルーティング
- this.removeCurrentView();
- this.nextView(app.TodoCollectionView);
+ //Todoレイアウト用ビューにルーティング
+ this.nextView(app.TodoLayoutView);
},
todoDetail : function(id) {
- 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);
+ app.application.mainRegion.show(new View(option));
},
- removeCurrentView : function() {
- if (this.currentView) {
- this.currentView.remove();
- }
- }
});
})(app);
- removeCurrentView関数(削除)
- Marionetteのリージョンの機能により、Viewの生成、破棄が管理されるので、自前でやっていた
removeCurrentView
関数の定義と呼び出し部分を削除しています。
- Marionetteのリージョンの機能により、Viewの生成、破棄が管理されるので、自前でやっていた
- nextView関数
- 元々はViewの生成を行っているのみでしたが、リージョン機能を使用し、「Viewを生成し、そのViewを引数としてリージョンをshowする」という処理に変更しています。
todo-layout-view.js
レイアウト用ビューです。
ここでの役割は下記です。
- リージョンの定義
- コレクションの読み込み
- コレクションの読み込みは、元々
todo-collection-view.js
で行っていましたが、ここで行うようになります。
- コレクションの読み込みは、元々
var app = app || {};
//Todo一覧表示用レイアウトビュー
(function(app) {
app.TodoLayoutView = Backbone.Marionette.LayoutView.extend({
//テンプレート
template: '#todo-layout-template',
regions : {
listRegion : '#todo-lists',
},
onRender : function(){
var todoCollection = new app.TodoCollection();
this.listenTo(todoCollection , 'reset', this.showTodoList, this);
todoCollection.fetch({reset : true});
},
showTodoList : function(todoCollection){
this.listRegion.show( new app.TodoCompositeView({
collection : todoCollection
}));
},
});
})(app);
- template変数
- テンプレートは、
template
変数にidを設定しておくだけで、Marionetteが描画時にそのidで示されるテンプレートを自動で適用してくれます。
- テンプレートは、
- regions変数
- ここでリージョンとして使用するタグのidを指定します。ここでは、一覧表示領域となる
#todo-lists
を指定しています。
- ここでリージョンとして使用するタグのidを指定します。ここでは、一覧表示領域となる
- onRender関数
- Marionetteの全ての種類のビューで実装されます。ビュー生成時に自動で実行されます。
- ここでは、コレクションのデータを取得し、取得完了時に
showTodoList
関数を実行するようにしています。
- showTodoList関数
-
onRender
関数内で指定したthis.listenTo(todoCollection , 'reset', this.showTodoList, this);
により、collectionのreset
イベント(コレクションの中身がリフレッシュされた)の発火時に実行されるハンドラです。 -
regions
変数で設定した、listRegion
を、show
関数によって表示しています。show
関数の引数にapp.TodoCompositeView
の新しいオブジェクトを渡し、listRegion
内のビューとして設定しています。
-
todo-composite-view.js
上記のtodo-layout-view.js
の中のlistRegion
リージョンの中に表示されるビューです。
CompositeViewを使用しています。
ポイントは、childView
、childViewContainer
、ui
です。
todo-collection-view.js
に記載していた追加ボタンやそのイベントハンドラはここに書きます。
var app = app || {};
//Todo一覧表示用ビュー
(function(app) {
app.TodoCompositeView = Backbone.Marionette.CompositeView.extend({
template: '#todo-composite-template',
childView : app.TodoItemView,
childViewContainer : 'tbody',
ui : {
addTodo : '#addTodo',
newTodo : '#new-todo'
},
events : {
'click @ui.addTodo' : 'onCreateTodo',
},
initialize: function(){
_.bindAll( this, 'onCreatedSuccess' );
},
onCreateTodo : function(e) {
this.collection.create(this.newAttributes(), {
silent: true ,
success: this.onCreatedSuccess
});
this.ui.newTodo.val('');
},
newAttributes : function() {
return {
todo : this.ui.newTodo.val().trim(),
status : 0
};
},
onCreatedSuccess : function(){
this.collection.fetch({ reset : true });
},
});
})(app);
-
childView変数
-
CompositeView
では、子ビューとしてItemView
を指定します。素のBackboneでは、子ビューの描画を親ビューで行うコードを記載していましたが、CompositeViewを使用すると、子ビューとなるItemViewを指定するだけでその辺り書かなくてよくなります。
-
-
childViewContainer変数
- 子ビューとなるItemViewを描画するコンテナとなる、テンプレート上のHTMLエレメントのIDを指定します。ここでは、
tbody
タグを指定しています。
- 子ビューとなるItemViewを描画するコンテナとなる、テンプレート上のHTMLエレメントのIDを指定します。ここでは、
-
ui
変数-
ui
変数は、Marionetteで用意されている変数です。ここに、"変数名 : セレクタ"の形式で指定すると、このビュー内では指定した変数名で参照出来るようになります。- events変数内でセレクタ指定する場合、
@ui.変数名
という形式で指定できるようになります。ここでは、'click @ui.addTodo' : 'onCreateTodo'
と使用しています。
- events変数内でセレクタ指定する場合、
-
-
todo-collection-view.js
に記載していた、onCreateTodo
、newAttributes
、onCreatedSuccess
はここに書きます。
todo-item-view.js
ItemViewを適用しています。
todo-composite-view.js
同様、ui
変数を使用できます。
テンプレートの描画をItemView
がやってくれますので、描画処理が削除されています。
//Todo一覧の1件表示用ビュー
(function(app) {
- app.TodoItemView = Backbone.View.extend({
+ app.TodoItemView = Backbone.Marionette.ItemView.extend({
//DOMに要素追加のタグ名
tagName : 'tr',
//テンプレート
- template : _.template($('#item-template').html()),
+ template : '#todo-item-template',
+
+ ui : {
+ checkBox : '.toggle',
+ removeLink : '.remove-link'
+ },
//DOMイベントハンドラ設定
events : {
//チェックボックスクリック時
- 'click .toggle' : 'onStatusToggleClick',
+ 'click @ui.checkBox' : 'onStatusToggleClick',
//削除ボタンクリック時
- 'click .remove-link' : 'onRemoveClick',
+ 'click @ui.removeLink' : '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);
- ui変数、events変数
-
todo-composite-view.js
と同様に書き換えています。
-
- initialize関数, render関数
- ItemViewが描画処理をやってくれるので、自前の描画処理やViewの破棄処理等は削除しています。
todo-collection-view.js
今回の修正で、ビューのファイル名はMarionetteのビュー名に合わせるようにしました。CompositeView
を使用するようにしますので、
todo-collection-view.js
は削除しています。
実装
では、実装しましょう!
-
app/View/Layouts/default.ctp
を上記の通り修正。 -
app/webroot/js/app.js
を上記の通り修正。 -
app/webroot/js/routers/controller.js
を上記の通り修正。 -
app/webroot/js/views/todo-layout-view.js
を上記の通り作成。 -
app/webroot/js/views/todo-composite-view.js
上記の通り作成。 -
app/webroot/js/views/todo-item-view.js
を上記の通り修正。 -
app/webroot/js/views/todo-collection-view.js
を削除。 - 動作確認!
- Gitにコミット
完成ソース(GitHub)へのリンク
Lesson3へ!
Lesson 3 TODO詳細画面にMarionetteのビューを適用
TODO詳細画面に、Marionetteのビューを適用して修正します。
Lesson2までの内容でほぼ修正できると思います。
編集するファイル一覧
編集 | file | 役割 |
---|---|---|
修正 | app/View/Layouts/default.ctp | HTMLテンプレート |
修正 | app/webroot/js/routers/controller.js | コントローラ |
追加 | app/webroot/js/views/todo-detail-layout-view.js | ビュー(LayoutView:Todo1件の詳細画面のレイアウト用) |
追加 | app/webroot/js/views/todo-detail-item-view.js | ビュー(ItemView:Todo1件表示用) |
削除 | app/webroot/js/views/todo-detail-view.js | ビュー(Todo1件の詳細画面表示用) |
default.ctp
詳細画面は元々View一つで実装していましたが、LayoutViewとItemViewに分けます。
</td>
</script>
- <!-- 詳細画面 -->
- <script type="text/template" id="detail-template">
+ <!-- 詳細画面のレイアウトテンプレート -->
+ <script type="text/template" id="todo-detail-layout-template">
+ <div id="todo-item"></div>
+ </script>
+
+ <!-- 詳細画面の表示内容テンプレート -->
+ <script type="text/template" id="todo-detail-item-template">
<h2>Todo #<%- id %></h2>
<div>
<textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
〜中略〜
<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-detail-item-view.js" type="text/javascript"></script>
+ <script src="js/views/todo-detail-layout-view.js" type="text/javascript"></script>
<script src="js/views/todo-composite-view.js" type="text/javascript"></script>
<script src="js/views/todo-layout-view.js" type="text/javascript"></script>
<!-- controller -->
- 詳細画面のテンプレートを、LayoutViewで使用する部分とItemViewを使用する部分に分割しています。
- LayoutView用 ->
todo-detail-layout-template
- ItemView用 ->
todo-detail-item-template
- LayoutView用 ->
- レイアウト用のビューに、リージョンとする部分のタグを追加し、idを
todo-item
としています(ここに詳細表示(ItemView)のビューが読み込まれます)。 - 詳細表示用のビュー(
todo-detail-view.js
)はtodo-detail-item-view.js
に変更しています。 - レイアウト用のビュー、
js/views/todo-detail-layout-view.js
を追加しています。
controller.js
ビューへのIDの渡し方を変更しています。
},
todoDetail : function(id) {
- this.nextView(app.TodoDetailView, id);
+ this.nextView(app.TodoDetailLayoutView, {modelId : id});
},
nextView : function(View, option) {
app.application.mainRegion.show(new View(option));
},
});
})(app);
- todoDetail関数
- nextView関数を実行していますが、元々はidをそのまま第二引数に渡していました。修正後は、
{modelId : id}
と、オブジェクトにして渡しています。これは、View側では元々Initialize関数の引数としてそのまま使用していましたが、他の箇所で使用するためにViewのoptions
変数に渡すための修正です。
- nextView関数を実行していますが、元々はidをそのまま第二引数に渡していました。修正後は、
todo-detail-layout-view.js
レイアウト用ビューです。
ここでの役割は下記です。
- リージョンの定義
- モデルの読み込み
- モデルの読み込みは、元々
todo-detail-view.js
で行っていましたが、ここで行うようになります。
- モデルの読み込みは、元々
var app = app || {};
//詳細画面用レイアウトビュー
(function(app) {
app.TodoDetailLayoutView = Backbone.Marionette.LayoutView.extend({
//テンプレート
template : '#todo-detail-layout-template',
regions : {
itemRegion : '#todo-item',
},
onRender : function() {
var todoModel = new app.TodoModel({
id : this.options.modelId
});
//モデルのサーバからのデータ取得完了時、描画を行う
this.listenTo(todoModel, 'sync', this.showItem, this);
//サーバからデータ取得
todoModel.fetch({
wait : true
});
},
showItem : function(todoModel) {
this.itemRegion.show( new app.TodoDetailItemView({
model : todoModel
}));
},
});
})(app);
- regions変数
- リージョンとして
#todo-item
を指定しています。
- リージョンとして
- onRender関数
-
this.options.modelId
と指定し、controller.js
内で指定したモデルのIDを取得しています。- Viewの生成時に指定したオブジェクトは、View内で、
this.options
として参照することが出来ます。
- Viewの生成時に指定したオブジェクトは、View内で、
-
- データの取得から子ビューの呼び出しの流れは、
collection
とmodel
の違いはありますが、同様に書きます。
todo-detail-item-view.js
ItemViewを適用しています。
var app = app || {};
//詳細ビュー
(function(app) {
app.TodoDetailItemView = Backbone.Marionette.ItemView.extend({
//テンプレート
template: "#todo-detail-item-template",
ui : {
todoStatus : '#edit-todo',
updateButton : '#updateTodo',
cancelButton : '#updateCancel'
},
//DOMイベントハンドラ設定
events : {
//更新ボタンクリック時
'click @ui.updateButton' : 'onUpdateClick',
//キャンセルボタンクリック時
'click @ui.cancelButton' : 'onCancelClick',
},
//初期化
initialize: function(){
_.bindAll( this, 'onSaveSuccess' );
},
//更新ボタンクリックのイベントハンドラ
onUpdateClick : function() {
//テキストボックスから文字を取得
var todoString = this.ui.todoStatus.val();
this.model.save({
todo : todoString
}, {
silent : true,
success : this.onSaveSuccess,
});
},
//キャンセルボタンクリックのイベントハンドラ
onCancelClick : function() {
this.backTodoLists();
},
//更新成功
onSaveSuccess : function() {
this.backTodoLists();
},
//TODOリスト画面に戻る
backTodoLists : function() {
Backbone.history.navigate('#todo-lists', true);
}
});
})(app);
修正内容はLesson2とほぼ同様の内容ですので解説は割愛します。
todo-collection-view.js
ビューのファイル名はMarionetteのビュー名に合わせますので、todo-detail-view.js
は削除しています。
実装
では、実装しましょう!
-
app/View/Layouts/default.ctp
を上記の通り修正。 -
app/webroot/js/routers/controller.js
を上記の通り修正。 -
app/webroot/js/views/todo-detail-layout-view.js
を上記の通り作成。 -
app/webroot/js/views/todo-detail-item-view.js
上記の通り作成。 -
app/webroot/js/views/todo-detail-view.js
を削除。 - 動作確認!
- Gitにコミット
完成ソース(GitHub)へのリンク
これで完成です!
お疲れ様でした
お疲れ様でした。うまくいきましたでしょうか。
最後に、今回使用したMarionetteの主な機能をまとめておきます。
理解できているか、チェックしてみましょう。
今回使用したMarionette機能まとめ
最後に、今回使用したMarionetteの主な機能をまとめておきます。
理解できているか、チェックしてみましょう。
- オブジェクト
- Marionette.Application
- スタートポイント。土台となるリージョンを定義する。
- Marionette.AppRouter
- ルーティングを行う
- Marionette.Controller
- ルーティング後に実行する関数を定義する
- Marionette.Region
- ビューを一つ管理する
- Marionette.LayoutView
- リージョン内の一番外側のビューとして使用する。リージョンも定義できる
- テンプレートを使用できる。
- Marionette.CollectionView
- ※今回使用していません。CompositeViewからテンプレート機能を除いたものです。
- 内部にただItemViewを表示するだけ(リストボックス的な表示)の場合等に使用するとよいでしょう。
- Marionette.CompositeView
- コレクションの表示に使用する。コレクション内の1件表示用にItemViewを内包する。
- テンプレートを使用できる。コレクションを操作するためのボタン等(追加ボタン等)を配置するのがパターン。
- Marionette.ItemView
- モデルの表示に使用する。
- テンプレート機能を使用できる。モデル1件を操作するためのボタン(更新ボタン等)等を配置するのがパターン。
- Marionette.Application
- ビュー内個別機能
- ui変数
- ビュー内のHTMLエレメントのセレクタの記述を一箇所に集約する。
-
@ui.〜
の形式でビュー内から参照可能となる。
- regions変数
- 管理するリージョンを定義する。
- template変数
- テンプレートのIDを指定しておくことで、自動的にレンダリングしてくれる。
- options変数
- ビューの初期化時に渡されたオブジェクトを参照することが出来る。
- ui変数
今回使用しなかったもの
triggers
、Behaviors
、etc...
いろいろです。調べてみましょう!
飲みDev
今回から、勉強会終了後にお酒を飲みながら居残り開発をする、名づけて「飲みDev」をやります!
テーマを挙げておきます。
下記、いけるところまで行きましょう!
もちろん、他の機能をつけたり、なんでもやってみましょう!
- リロードボタンをつける
- "完了分を表示しない"チェックボックスをつける
- チェックボックスを"Done"ボタンに変更、完了分は色を変える。完了分は"Todo"ボタン表示、押すと未完了に戻る。
- なにかおもしろ機能!
いい感じにできたら発表してください!
以上です!
コメント/フィードバックお待ちしております。
参加者の方も、そうでない方もお気づきの点があればお願い致します。