試して学ぶ Backbone.js入門4
はじめに
前回の「試して学ぶ Backbone.js入門3」
では、Backbone.jsによるアプリケーションのアーキテクチャを決定づけるBackbone.Eventsについて説明しました。
今回はアプリケーションの画面表示周りを制御するBackbone.View(以降、View)について説明します。
今回のソースの全体はこちらで確認することができます。
Backbone.View
Backbone.jsが提供するViewコンポーネントは、ユーザインタフェースを制御することが目的であり、その役割は大きく以下の2つがあります。
- DOMイベントをハンドリングする
- Model/Collectionによるイベントをハンドリングする
Backbone.ViewにはModelの自動バインディングのようなリッチな機能はありません。
上記のDOMイベント、Model/Collectionイベントを自分でハンドリングする必要があります。。
シンプルな仕組みを使って自分で処理を記述することにより、分かりやすく、かつ自由度の高い実装ができることが特長です。
特に、レンダリングに関する機能を持っていないため、他のテンプレートライブラリ、GUIライブラリを自由に組み合わせて利用することが可能です。
それでは今回は、これまでに学んできたModel/Collectionを利用して、簡単なMemoアプリケーションを開発してみましょう。
今回開発する画面のイメージは以下の通りです。
MemoAppView
CSSフレームワークのBootstrap
と、Underscore.jsのテンプレート機能
を使います。
Viewのプロパティとメソッド
Viewは必ず一つのDOMと対応づける必要があります。
今回の開発する画面を踏まえると以下の3つのDOMに対して、それぞれViewを作ることにします。
これらのViewを定義する上で、Viewの定義に必要な項目を確認します。
Viewが標準で持つプロパティには以下があります。
プロパティ | 説明 |
---|---|
id | タグ内のid属性 |
className | タグ内のclass属性 |
tag | タグ名称(省略した場合はdiv) |
el | Viewが対応するDOM Element |
events | DOMイベント発生時対応させるメソッドのhash |
model | Viewに関連付けるModelオブジェクト |
collection | Viewに関連付けるCollectionオブジェクト |
また、Viewが標準で持つメソッドには以下があります。
メソッド | 説明 |
---|---|
initialize | 初期化処理(コンストラクタ)。 newによってViewオブジェクトが生成された時に呼び出される。 |
render | 描画処理を実装するメソッド。デフォルトでは何もしない。 |
上記以外でよく利用するプロパティとして$elと$があります。
$elはelプロパティをjQueryオブジェクトとしてラップしたものです。
$はjQueryオブジェクトそのもののショートカットになります。
それでは今回開発する以下の3つのViewの作成を通して、Viewをどのように定義して利用できるか確認していきましょう。
- AppView:アプリケーション全体の管理をView。
- ListView:Memoオブジェクトをリストとして表示するView。
- ItemView:1つのMemoオブジェクトを表示するView。
今回の画面のHTML
Viewを定義する前に今回の画面のHTMLを確認しましょう。
今回のHTMLの全体はこちらで確認することができます。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Backbone View Example</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="lib/css/bootstrap.css" rel="stylesheet"> <style> body { padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */ } </style> <link href="lib/css/bootstrap-responsive.css" rel="stylesheet"> </head> <body> <div class="navbar navbar-inverse navbar-fixed-top"> <div class="navbar-inner"> <div class="container"> <a class="brand" href="./view.html">Backbone Memo Application</a> </div> </div> </div> <div id="main" class="container"> <h1>Memo</h1> <form id="addForm" class="form"> <input type="text" name="title" class="input-block-level" placeholder="Title"/> <textarea rows="5" name="content" class="input-block-level" placeholder="Content"></textarea> <a href="#" id="addBtn" class="btn btn-primary">Add</a> </form> <div id="memoList" class="row-fluid"> </div> </div> <script type="text/template" id="tmpl-itemview"> <h3><%= title %></h3> <p><%= content %></p> <p><a href="#" class="delete">delete</a></p> </script> <script src="lib/js/jquery-1.9.1.js"></script> <script src="lib/js/json2.js"></script> <script src="lib/js/underscore.js"></script> <script src="lib/js/backbone.js"></script> <script src="js/view.js"></script> </body> </html>
Bootstrapの説明はここでは省略します。ただ、今回はCSSフレームワークとして必要最低限の機能しか使っていませんので、何を利用しているかは直ぐに理解できると思います。
“#addForm”がMemoの入力フォーム、”#memoList”がMemoのリストを表示する部分になります。
AppViewの定義
それではまず、アプリケーション全体を管理するAppViewを定義しましょう。コードは以下のようになります。
var AppView = Backbone.View.extend({ events:{ "click #addBtn":"onAdd" }, initialize:function () { _.bindAll(this); this.$title = $("#addForm [name='title']"); this.$content = $("#addForm [name='content']"); this.collection = new MemoList(); this.listView = new ListView({el:$("#memoList"), collection:this.collection}); this.render(); }, render:function () { this.$title.val(''); this.$content.val(''); }, onAdd:function () { this.collection.create({title:this.$title.val(), content:this.$content.val()}, {wait:true}); this.render(); } });
Viewの定義は、Backbone.View.extendによって行います。
eventsプロパティには、Memoを登録するためのform内のボタンをクリックした時に発生するclickイベントをonAddメソッドに対応づけるように定義しています。
コンストラクタとなるinitializeメソッドでは、以下のことを行っています。
- _(Underscore.js)のbindAllメソッドを使い、各メソッド実行時のコンテキストがAppView自身になるようにthisを指定します。
これはDOMイベントが発生した際にコンテキストがAppView以外になっている点を補正するために行います。 - this.$title, this.$contentはform内のinput、textareaの内容を後で取得できるように、DOM ElementをjQueryオブジェクトでラップしたものを保持しておくようにします。
- MemoListオブジェクトを生成し、MemoListオブジェクトを描画するためのListViewオブジェクトを生成します。
renderメソッドはformのinput、textareaの内容を初期化(クリア)します。
onAddメソッドは、formに値を入力し、Addボタンをクリックされた時に呼び出されるメソッドです。
formから取得した値を使って、MemoListからMemoオブジェクトを生成します。
このAppViewオブジェクトを生成するには以下のようにコードを書きます。
var appView = new AppView({el:$("#main")});
ListViewの定義
それでは次に、MemoオブジェクトのリストであるMemoListを描画するListViewを定義しましょう。
コードは以下のようになります。
var ListView = Backbone.View.extend({ initialize:function () { this.listenTo(this.collection, "add", this.addItemView); var _this = this; this.collection.fetch().done(function () { _this.render(); }); }, render:function () { this.collection.each(function (item) { this.addItemView(item); }, this); return this; }, addItemView:function (item) { this.$el.append(new ItemView({model:item}).render().el); } });
コンストラクタ(initializeメソッド)では、CollectionにMemoオブジェクトが追加された時に発行されるaddイベントを監視するように設定し、コールバックメソッドとしてaddItemViewメソッドを指定しています。
また、初期表示時にサーバ側に対してfetchを行い、情報が正常に取得できたら初期表示(renderメソッド)を行うようにしています。
renderメソッドでは、Collectionに登録されたMemoオブジェクトを一つずつ取り出し、addItemViewメソッドによって各ItemViewオブジェクトを生成します。
ItemViewの定義
最後にItemViewの定義を見てみましょう。コードは以下のようになります。
var ItemView = Backbone.View.extend({ tmpl:_.template($("#tmpl-itemview").html()), events:{ "click .delete":"onDelete" }, initialize:function () { _.bindAll(this); this.listenTo(this.model, "change", this.render); this.listenTo(this.model, "destroy", this.onDestroy); }, onDelete:function () { this.model.destroy(); }, onDestroy:function () { this.remove(); }, render:function () { this.$el.html(this.tmpl(this.model.toJSON())); return this; } });
まず最初にtmplオブジェクトを定義しています。
これは_(Underscore.js)のテンプレート機能を利用しているもので、scriptタグ内に記載したテンプレート情報を元に、Modelを関連付けてビューを構築することができます。
$(“#tmpl-itemview”)によってscriptタグの中身を取得し、テンプレートオブジェクトを生成しています。
scriptタグによるテンプレート情報は以下の通りです。
<script type="text/template" id="tmpl-itemview"> <h3><%= title %></h3> <p><%= content %></p> <p><a href="#" class="delete">delete</a></p> </script>
<%= =>で囲んだ部分にModelのプロパティ名を定義し、renderメソッドに書いているようにModelオブジェクトをtoJSONしたものを与えることで1行のMemoオブジェクトを描画したItemViewを返します。
eventsでは、1行単位のItemView内にあるdeleteリンクをクリックした時の対応づけるメソッド名を定義し、initializeメソッドにてmodelに対してdestroyイベントを監視するように設定しています。
このようにすることで、deleteリンクのクリック => modelのdestroy => viewのremoveと一連の処理を記述することができます。
view.removeメソッドはDOMの削除を行い、内部でstopListeningを呼びすことで、listenToで設定していたイベント監視を解除をしてくれます。
ここではItemViewが描画しているModelと1対1で対応しているため、このようにシンプルに書くことができます。
まとめ
これで今回のアプリケーションは動作するようになります。
アプリケーションとして利用するModel/CollectionはViewについて知る必要がなく、ViewがDOMイベントとEventsの仕組みでModel/Collectionのイベントをハンドリングすることでアプリケーションを開発することができます。
コードの記述量という意味では規模が小さくなるわけではないのですが、一定のパターンで開発でき、見通しが良いことがBackbone.jsによってアプリケーションを開発するメリットであると思います。
次回
次回は最終回として、Backbone.Routerについて取り上げます。
著者: 村田賢一郎
Acroquest Technology 株式会社勤務。Javaによるミッションクリティカルな集中監視システムのフレームワーク開発、およびライフラインを支えるシステム開発に携わる。非同期処理、メッセージング、HAなどが本業である傍ら、Webによる新しいUI表現、開発手法に興味があり、あれこれ模索している。