Backbone.jsに入門してみる【Collection編】
Backbone.Collectionは本格的に使ったことがないです^^;
ほんとの入門になります。
Backbone.Collectionとは
CollectionはModelの集合を扱いたい場合に使います。
前回は電話帳を作ろうとしていたのですが、
電話情報がModelであって、それを束ねる電話帳がCollectionとなります。
また、CollectionはModelの集合に順序をもたせることができます。
Backbone.Collectionの使い方
前回作ったソースに追記して、Collectionを定義します。
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 // 【1】 }); // Addressのインスタンスを作りつつAddressBookのインスタンスを生成 var addressBook = new AddressBook([ // 【2】 { name: 'yutapon', tel: '090-xxxx-xxxx' }, { name: 'hogepon', tel: '080-xxxx-xxxx' } ]); addressBook.at(1).get('name'); // -> hogepon
Backbone.Collectionを拡張してAddressBookというオブジェクトを作ることで
Collectionを定義できます。
【1】ではmodel属性にBackbone.Model型のオブジェクトを指定して、
どのModelを扱うCollectionなのか指定します。
【2】のCollectionのインスタンスを生成するときは
コンストラクタに【1】で指定したModelのインスタンスを自動的に生成できるので、
Modelのコンストラクタに渡す属性を配列で指定します。
Collectionは順序を持って管理するので、渡した順番でインスタンスが生成されます。
Collectionへの追加
CollectionにModelを追加するには以下の4つのメソッドがあります。
- add() ・・・ comparatorが指定されていれば追加後にソートする
- push() ・・・ 一番後ろに追加する
- unshift() ・・・ 先頭に追加する
- create() ・・・ add()と一緒、プラスsave()メソッドが呼ばれる
実際のコードはこんな感じ。
// Addressの定義は省略 var AddressBook = Backbone.Collection.extend({ model: Address }); var addressBook = new AddressBook([ { name: 'yutapon', tel: '090-xxxx-xxxx' }, { name: 'hogepon', tel: '080-xxxx-xxxx' } ]); addressBook.add({ name: 'piyopon', tel: '070-xxxx-xxxx'}); addressBook.pluck('name'); // -> ["yutapon", "hogepon", "piyopon"] addressBook.unshift({ name: 'fugapon', tel: '050-xxxx-xxxx'}); addressBook.pluck('name'); // -> ["fugapon", "yutapon", "hogepon", "piyopon"]
簡単ですね。
ちなみにaddressBook.pluck()はCollection内の全てのModelに対して
指定した属性の値を配列形式で返してくれます。
comparator ?
Collectionは順序を持つのですが、その基準を定義するのがcomparatorです。
指定しなければCollectionはModelが追加された順番を基準にします。
comparatorを指定すれば好きな基準でソートを掛けられます。
電話帳なので、名前順にソートするように修正してみます。
// Addressの定義は省略 var AddressBook = Backbone.Collection.extend({ model: Address, comparator: 'name' // name属性を基準にソートするように指定 }); var addressBook = new AddressBook([ { name: 'yutapon', tel: '090-xxxx-xxxx' }, { name: 'hogepon', tel: '080-xxxx-xxxx' } ]); addressBook.add({ name: 'piyopon', tel: '070-xxxx-xxxx'}); addressBook.pluck('name'); // -> ["hogepon", "piyopon", "yutapon"] addressBook.unshift({ name: 'fugapon', tel: '050-xxxx-xxxx'}); addressBook.pluck('name'); // -> ["fugapon", "hogepon", "piyopon", "yutapon"]
add()で追加するとcomparatorで追加位置が評価されていますね。
unshift()で追加するとcomparator関係なしに先頭に追加されています。
addイベント
CollectionへのModel追加があると、Collectionはaddイベントを発火します。
Collectionに何か追加されたら処理を走らせたい場合は、このaddイベントを
購読しておきましょう。
var AddressBook = Backbone.Collection.extend({ model: Address, comparator: 'name', initialize: function(){ _.bindAll(this); this.on('add', this.callback); }, callback: function(){ // Collectionに追加がある度実行される console.log('happen add event'); } });
Collectionからの削除
CollectionからModelを削除するには以下の4つのメソッドを使います。
- remove() ・・・ 第一引数に削除したいmodelを渡す
- pop() ・・・ 一番後ろのmodelを削除
- shift() ・・・ 先頭のmodelを削除
- destroy() ・・・ remove()と一緒、プラスdestroy()メソッドが呼ばれる
たとえばyutaponという名前の電話情報を消したい場合は
addressBook.remove(addressBook.where({ name: 'yutapon' }));
という形になります。
追加の時と同様、CollectionからModelの削除があると remove イベントが発火します。
Collectionの中を一括置換する
Collectionを一括置換する場合は以下の2つのメソッドが用意されています。
- reset() ・・・ 一旦空にしてから追加し直す
- set() ・・・ 変更があるかどうか順に見ながら置換する
どちらも reset イベントが発火します。
collection.set()
set()の方は細かくイベントが発火します。
- 新規にModelが追加されたとき → addイベントが発火
- すでにあるModelが削除されたとき → removeイベントが発火
- すでにあるModelが変更されたとき → mergeイベントが発火
この細かく発火するイベントは抑制できます。
var addressBook = new AddressBook([ { name: 'yutapon', tel: '090-xxxx-xxxx' }, { name: 'hogepon', tel: '080-xxxx-xxxx' } ]); addressBook.set([ { name: 'yutapon', tel: '090-xxxx-xxx9'} ], { remove: false }); // removeイベントが発火しないように指定
この例だとremoveイベントとmergeイベントが発火しそうな気がしますが、
removeイベントとaddイベントが発火します。
set()のときに指定したオブジェクトは既にCollectionに存在する
Modelの参照を持っているわけではないので、新しいModelとして追加されます。
CollectionからModelを取得する
Collectionから任意のModelを取得するには以下の4つのメソッドが用意されています。
- get() ・・・ 各Modelが持つcidを指定して取得する
- where() ・・・ 属性と値の組みに一致するModelを全て取得する
- filter() ・・・ Collection内のModelを条件と比較し、真になったModelを全て取得する
- findWhere() ・・・ where()と似ているが最初に一致したModelのみ取得する
get()だけが少し特殊で、事前にModelのcidを知っている必要があります。
where()がよく使う形式で、filter()はやや使いにくい印象です。
var addressBook = new AddressBook([ { name: 'yutapon', tel: '090-xxxx-xxxx' }, { name: 'hogepon', tel: '080-xxxx-xxxx' } ]); // result1 -> [ address ] のように、配列に該当するModelが入っている var result1 = addressBook.filter(function(address){ return address.get('name') === 'yutapon'; }); // result2 -> [ address ] result1と同じ結果になる var result2 = addressBook.where({ name: 'yutapon' }); // result3 -> address のように、該当するModelが取得できる var result3 = addressBook.findWhere({ name: 'yutapon' });
Modelで発火するイベントはCollectionでも発火する
各Modelでイベントが発火すると、Collectionでも同じイベントが発火するので、
Collectionにイベントの監視を集約させておくことができます。
var AddressBook = Backbone.Collection.extend({ model: Address, comparator: 'name', initialize: function(){ _.bindAll(this); this.on('change', this.cbChange); }, cbChange: function(model){ console.log('happen change event'); var index = this.indexOf(model); // ここで何番目のModelに変更があったか検知できる } });
おわりに
まだCollection.fetch()などDBからデータを取ってくるメソッドなどが
あるのですが、例によって別枠で扱います。
Collectionは使いこなすと便利そうですが、うまく設計しないと
逆にメンテナンス性が落ちそうな印象。
早く試行錯誤して取り入れたいです。
次回はBackbone.View編になります。