Mackbone.Modelの派生クラスは、デフォルトでAjaxによるサーバとの連携ができる。

前のエントリでも書いたのだけど、fetch(), save(), destory() の3つのメソッドを使う。
この3つのメソッドから、Backbone.sync()が呼ばれ、$.ajax()用にオプション処理するが、この中ではサーバー側アプリケーションの要件に応じて、Backbone.emulateHTTP、Backbone.emulateJSON というフラグを活用し、古い実装でのアプリケーションにも対応しやすくする様子。

Modelに実装される、サーバー連携用メソッドは3つ。
  1. model.fetch() サーバーから読む。
  2. model.save() サーバーに書き込む。
  3. model.destroy() サーバーから削除する。

この3つは、Modelの派生クラスにsync() を実装した場合はそれを、未実装の場合はBackbone.sync() を読んでAjax処理する。

最初は、標準の Backbone.sync() を使う方向で挙動を確認する。


Backbone.sync( method, model, options );

引数の仕様は、Modelの派生クラスに sync() メソッドを実装する場合に固定される。

method には、"read", "create", "update", "delete" を渡す。
Backbone.syncでは、1293行の methodMap に基づいて、それぞれ、'GET', 'POST', 'PUT', 'DELETE' というHTTPメソッドに置き換えている。1319行では、options 未指定時に 空のオブジェクトを初期値としていて、任意の箇所から単体でBackbone.sync()を利用できるような配慮(テストとかね)。
あと、1322行以降で $.ajax() 用のオプション自動生成を開始する。

自動構築される $.ajax() のオプション

params = {
    type : HTTPメソッド。1316行
    dataType : 'json',
    url : model.url() により得られる値
    contentType : 'application/json' または、’application/x-www-form-urlencoded’
    data : JSON.stringify(model.toJSON()) または、{model: params.data}
    processData : GETメソッド以外は false (エスケープしない)。
}
// retrun $.ajax( _.extend( params, options ) );

とまぁ、こんな感じ。細かなところは、HTTPメソッドBackbone.emulateJSONフラグ、Backbone.emulateHTTPフラグで変わる。

最終的には、1359 行の_.extend() によって options で上書きするので、かなり応用の利くAjaxユーティリティといった位置づけかと。自動生成するロジックを無効化しようと思えばできる。


Model の url() メソッド

サーバー側にリクエストするURLを取得する機能で、Backbone.sync() のオプションで url が未設定(要するに、fetch(), save(), destory() のオプションにurl が未設定)の場合に呼ばれる。
fetch({ url : null }), save({ url : null }), destroy({ url : null }) は自動生成するので、1)model の urlRootプロパティ、または 2)model.collection のurlプロパティ、url関数が設定されていなければならない。

この特徴は、「自動生成、手動指定が選べる」事を意味する。
自動生成のパターンは、4つ。

  1. model.urlRoot
  2. model.urlRoot + '/' + model.id
  3. model.collection.url
  4. model.collection.url + '/' + model.id



サーバーから GETで読み込む: fetch()

fetch({

    url : (前述の通り、手動設定の場合に),

// set() メソッドの為のオプション silent : (boolean), // 以下、Backbone.sync() 用のオプション // $.ajax() のsuccessコールバック success : function(model, resp) {} // $.ajax() のerrorコールバック // 未指定時は、model.on('error', function(){ }) を発火 → Backbone.wrapError() 参照 error : function(model, resp){} })

340行で model.set( model.parse(resp), options ) してるので、デフォルトのperseメソッドを使う場合は、サーバーレスポンスは { key:val, key2,val2, ... } なJSONで無ければならない。派生クラスに独自のperse() メソッドを実装することで、レスポンスされたデータを加工すれば対応可能。



サーバーに新規投稿・更新を要求する:save()

内部でset()メソッドを呼ぶので、set()メソッド同様に追加するデータを指定する。
尚、set() メソッドは、silent:true (イベント不発) のオプションに矯正される(370行)

  • save( key, val, options ) // modelに key,val をセットしてリクエスト
  • save( { key : val }, options ) // modelに複数の key, val をセットしてリクエスト
  • save( null, options ) // modelに格納済みの値だけでリクエスト

サーバーへ POST, PUT するデフォルト値は、1332行、1338行 (Backbone.sync() 内)参照。



save({

url : (前述の通り、手動設定の場合に),

// 早期検証モード wait : true, // _validate() を呼び、 POSTする値をチェックする。 // 派生クラスに validate() メソッドを実装しておくこと。 // 通信せずに処理中断できるようになる。 // 以下、Backbone.sync() 用のオプション // $.ajax() のsuccessコールバック // 未指定時は、model.on('sync', function(){ }) を発火 →386 - 390行 success : function(model, resp) {} // $.ajax() のerrorコールバック // 未指定時は、model.on('error', function(){ }) を発火 → Backbone.wrapError() 参照 error : function(model, resp){} // $.ajax() 用オプションを直接指示しても良い!? // 385 行 で model.set(serverAttrs, options) を実行してるので、怪しいところ。 })

サーバーに削除要求する:destroy()

モデルがDBの1レコードに相当するような場合に使え(this.id が存在することが条件)、サーバーへデータ削除を要求し、destroy イベントを発火する。
destroy イベント内でメモリストア(attributes プロパティ) からも削除する必要がある


model.on('destroy', function( model, collection, options ) {
    // model.clear() とか。
});


サーバーへ 要求するパラメータは前述したとおり、自動生成、手動指定が選べる。


destroy({

    wait : // destroyイベント発火タイミング
           // false サーバーへの要求時に。
           // true だと要求後、応答を待ってから削除。

    // 以下、Backbone.sync() 用のオプション

    // 削除用パラメータの手動指定例:
    url : '/endpoint',
    data : 'delId=' + encodeURIComponent( model.id ),

    // $.ajax() のsuccessコールバック
    // 未指定時は、model.on('sync', function(){ }) を発火 →420 - 424行
    success : function(model, resp) {}

    // $.ajax() のerrorコールバック
    // 未指定時は、model.on('error', function(){ }) を発火 → Backbone.wrapError() 参照
    error : function(model, resp){}
})



サーバー連携用メソッドに、success, error なコールバックを書くよりも、イベント発火させる方が良いかも。



あと、parse メソッド。通信の成功時にも呼ばれる。
fetch()やsave()を実行した時にも呼ばれ、この返値がモデルにセットされる。
initialize() 時にも呼ばれるけど、AJAXを使った場合は第二引数に $.ajax() による、XHRオブジェクトが渡されるので、これを切り分け条件にして、initialize() 時なのか、サーバー連携時なのかを判別すると良い。

var MyModel = Backbone.Model.extend({

    /* omitted */

    // default のparse() をオーバーライドしておく。
    parse : function( resp, xhr ) {
        var rslt = resp;
        if( xhr ) {
            // json-rpc の場合、 { id:'rpcID', result:{/*resultValue*/}, error:/*errorValue*/ } が返される。
            rslt = resp.error == null 
                ? resp.result
                : {};
        }
        return rslt;
    },

    /* omitted */
});


派生クラスを実体化するとき、第一引数が設定され、且つ、options.parse のフラグが立てられている場合に限り、parse() メソッドを呼ぶので、xhrの結果に対してのみperse() したい場合、実体化する際の引数で parse=false を指定する方法もある。
この場合、上記のような xhr がundefined か XMLHttpRequest かを切り分け条件にする必要は無い。



var m = new MyMode(attrs, { parse:true }); // 実体化


JSON-RPC だと…固定のエンドポイントでPOSTメソッドのみを使ったサーバー実装もあるが、このような POSTのみを使う場合は save() メソッドにオプションを指定するだけで、全処理に対応できる。