Mackbone.Modelの派生クラスは、デフォルトでAjaxによるサーバとの連携ができる。
前のエントリでも書いたのだけど、fetch(), save(), destory() の3つのメソッドを使う。
この3つのメソッドから、Backbone.sync()が呼ばれ、$.ajax()用にオプション処理するが、この中ではサーバー側アプリケーションの要件に応じて、Backbone.emulateHTTP、Backbone.emulateJSON というフラグを活用し、古い実装でのアプリケーションにも対応しやすくする様子。
Modelに実装される、サーバー連携用メソッドは3つ。
- model.fetch() サーバーから読む。
- model.save() サーバーに書き込む。
- 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つ。
model.urlRoot
model.urlRoot + '/' + model.id
model.collection.url
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() メソッドにオプションを指定するだけで、全処理に対応できる。
コメント