yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

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編になります。