Backbone.jsに入門してみる【Model+Collection+View連携編】
今回はModel, Collection, Viewの連携をしてみます。
設計
電話帳をずっと例にしてきたので今回も電話帳で。
電話帳は複数の電話情報の集合なので、
- 電話情報(Model)
- 電話帳(Collection)
と整理することができます。
これらを表示するために、
- 1件あたりの電話情報を表示するView
- 電話情報の集合をラップするようなView
が必要になります。
CollectionにModelが追加されたら
Viewの方にも自動的に反映されるようにしたいところです。
実装
前に作ったコードを使いながら書いてみました。
htmlの部分はこんな感じ。
<!-- 電話情報が個々に埋め込まれる --> <div id="addressBook"></div> <!-- jsRenderのテンプレート --> <script id="address-template" type="text/x-jsrender"> <div class="address"> <ul> <li>{{>name}}</li> <li>{{>tel}}</li> </ul> </div> </script> <!-- js読み込み --> <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script> <script src="javascripts/lib/jsrender.min.js"></script> <script src="javascripts/address.js"></script>
Backboneで書いた部分はこんな感じ。
// 電話情報を管理するModel var Address = Backbone.Model.extend({ defaults: function(){ return { language: 'jp' }; }, initialize: function(){ _.bindAll(this); this.on('initialize', this.setRegisterDate); this.trigger('initialize'); }, setRegisterDate: function(){ this.set('registerDate', new Date()); } }); // 電話情報をまとめて管理するCollection var AddressBook = Backbone.Collection.extend({ model: Address, comparator: 'name', initialize: function(){ _.bindAll(this); this.on('change', this.cbChange); }, cbChange: function(model, collection){ var index = this.indexOf(model); } }); // Model用のView var AddressView = Backbone.View.extend({ initialize: function(){ _.bindAll(this); }, render: function(){ var json = this.model.attributes; var html = $.templates(AddressView.tmplate).render(json); $(this.el).html(html); return this; } }, { tmplate: $('#address-template').html() }); // Collection用のView var AddressBookView = Backbone.View.extend({ el: '#addressBook', initialize: function(){ _.bindAll(this); this.listenTo(this.collection, 'add', this.addAddressView); }, addAddressView: function(model){ var addressView = new AddressView({ model: model }); this.$el.append(addressView.render().el); } }); // インスタンスを作って電話情報を追加する var addressBook = new AddressBook(); var addressBookView = new AddressBookView({ collection: addressBook }); addressBook.add([ { name: 'yutapon', tel: '090-xxxx-xxxx' }, { name: 'hogepon', tel: '080-xxxx-xxxx' } ]);
実行結果
CollectionにModelが追加されると
<div id="addressBook">の中身がこのようになります。
<div id="addressBook"> <div> <div class="address"> <ul> <li>yutapon</li> <li>090-xxxx-xxxx</li> </ul> </div> </div> <div> <div class="address"> <ul> <li>hogepon</li> <li>080-xxxx-xxxx</li> </ul> </div> </div> </div>
連携部分について
AddressView
まずはModel用のViewについて見てみます。
var AddressView = Backbone.View.extend({ initialize: function(){ _.bindAll(this); }, render: function(){ var json = this.model.attributes; // 【1】 var html = $.templates(AddressView.tmplate).render(json); // 【2】 $(this.el).html(html); // 【3】 return this; // 【4】 } }, { tmplate: $('#address-template').html() });
render()によってModelのHTMLを生成しています。
【1】ではこのViewのインスタンスの属性modelからattributesを取得しています。
attributesというのは属性値のことなので、model.get('hoge')などで取得できる値のことになります。
【2】ではjsRenderのテンプレートに変数を埋め込み、コンパイルしてHTMLを取得しています。
【3】では【2】で生成したHTMLをthis.elに埋め込んでいます。
この時、AddressViewにはel属性が無いので、デフォルトの<div></div>に埋め込まれます。
【4】ではAddressViewのインスタンスをリターンしています。
AddressBookView
つぎに、Collection用のViewについて見てみます。
var AddressBookView = Backbone.View.extend({ el: '#addressBook', initialize: function(){ _.bindAll(this); this.listenTo(this.collection, 'add', this.addAddressView); // 【1】 }, addAddressView: function(model){ // 【2】 var addressView = new AddressView({ model: model }); // 【3】 this.$el.append(addressView.render().el); // 【4】 } });
【1】ではコレクションのaddイベントを購読して、
addイベントが起きたらaddAddressViewメソッドを実行するようにしています。
【2】は【1】で設定されたメソッドです。コレクションに追加されたモデルは
addイベントの引数として取得できます。
【3】ではコレクションに追加されたモデルをViewのコンストラクタにセットし、
Viewのインスタンスを生成しています。
【4】ではaddressViewのrenderメソッドを実行した後にel属性を取得し、
それを#addressBookのセレクタにマッチするDOM上に追加しています。
アプリケーション実行部分
今までのは処理のフローを定義したビジネスロジック部分でした。
実際にデータの入出力をしてアプリケーションとして動作させる部分は
次の部分です。
// インスタンスを作って電話情報を追加する var addressBook = new AddressBook(); // Modelのインスタンス生成 var addressBookView = new AddressBookView({ // Collectionのインスタンス生成 collection: addressBook // addressBookのCollectionであることを指定 }); addressBook.add([ // addメソッドでModelを追加する { name: 'yutapon', tel: '090-xxxx-xxxx' }, // addressBookのコンストラクタ引数に渡される { name: 'hogepon', tel: '080-xxxx-xxxx' } ]);
addressBook.add()が起点となり、Modelが生成されCollectionにaddイベントが発火し、
それをCollection用のViewが検出して、Model用のViewのrenderメソッドを走らせ
DOMに追加するといった一連の流れが始まります。
おわりに
現状ではaddイベントにしか対応していないですが、
本来ならばremoveイベントにも対応するべきでしょう。
また、addressBook.add()の引数に指定している配列を
サーバのAPIから受け取ったデータにすれば、動的にViewを変化させることができます。
そしてBackbone.jsではCollection.fetch()などで実現できます。
次回、サーバ連携編といきたいところですが、
肝心のサーバができていないので久しぶりにnode.js回をやります。
そういえばRouter編もまだやってませんが近いうちに。