閲覧上の注意
この記事で対象としているバージョン0.5.3は結構古いので注意してください。例えばこの記事の内容でいうと、validate
に失敗したときに発生するイベントは'error'
から'invalid'
に変更されています。
その他の割りと新しい情報は Backbone.js Advent Calendar 2012 などにあります。
(追記ここまで)
今日のテーマは Model
です。
MVC に関する回で Backbone.js における MVC は Rails などの WAF における MVC よりは伝統的な MVC に近い というようなことを書きました。
しかしながら、モデルに関して言えばどちらの場合も大差ありませんから、気分が楽です。
Rails におけるモデルである ActiveRecord が OR マッパーとしての役割を持っていたり、データのバリデーション機能を持っていたりと、単なるデータを表す以上の機能を備えているように、Backbone.js における Model
もサーバと通信してデータを取得・永続化させたり、バリデーションを行うなどの機能を持っています。
今回は データの設定 に加え、この データの取得・永続化 と データのバリデーション に焦点を当てて Model
の使い方を解説したいと思います。
#基本的な使い方
View
の回でも述べたように、Model.extend
を使います。
var Blog = Backbone.Model.extend({
defaults: {
"dateTime": new Date().toISOString()
},
initialize: function (attrs, options) {
},
validate: function (attrs) {
if (attrs.text.length === 0) {
return "本文が入力されていません";
}
}
});
##initialize([attributes][, options])
インスタンスの初期化時に呼び出されます。
var Example = Backbone.Model.extend({
initialize: function (attrs, options) {
console.log("attrs", attrs);
console.log("options", options);
}
});
var e = new Example({a: 1}, {b: 2});
// => "attrs" {a: 1}
// => "options" {b: 2}
e.has("a") // => true
e.get("a") // => 1
e.has("b") // => false
e.get("b") // => undefined
has
や get
は後述。
ここのポイントは、attrs
はモデルの値として設定されるが、options
はそうではない、ということです。なので、例えばブログの本文や執筆時刻などのように データベースに保存する情報 は attrs
で渡し、データベースには保存しない設定項目 を渡す場合は options
を使うようにするとよいでしょう。
#データの設定
モデルにデータを設定したり、それを取得、変更したりする方法です。
##get(attrName)
モデルの属名を取得します。これは問題ないですよね。
##escape(attrName)
get
と同じですが、XSS などの危険がある箇所ではこちらを使いましょう。
var blog = new Blog({text: "<script>alert('xss')</script>"});
blog.get('text'); // => <script>alert('xss')</script>
blog.escape('text'); // => <script>alert('xss')</script>
##has(attrName)
モデルに属性が設定されているかどうか。返り値は Boolean です。
##set(attrs, [options])
属性を設定します。この時、change:attrName イベントが発生します。
blog.bind("change", function () {console.log("change")});
blog.bind("change:text", function () {console.log("change:text")});
blog.set({text: "foobar"}); // change:text と change 両方が出力される
blog.get("text"); // => foobar
イベントを発生させたくない場合は silent オプションを使います。
blog.set({text: "hoge"}, {silent: true}); // 何も出力されない
blog.get("text"); // => hoge
注意点は、silent を使うと後述する validate
を用いたバリデーションも 実行されない 、という点です。
##unset(attrName, [options])
attrName
の属性を削除して change:attrName イベントを発生させます。こちらもオプションで silent を指定することで抑制できます。
##clear(options)
全ての属性を削除して change イベントを発生させます。こちらもオプションで silent を指定することで抑制できます。
##previous(attrName)
ここまででモデルの属性まわりのメソッドの説明を行いましたが、その中で change イベントを発生させるものがいくつかありました。
この change イベントに bind
されたコールバック関数内でのみ意味を持つのが previous
です。
名前の通り、変更される前の値を取得することができます。
var Blog = Backbone.Model.extend({
initialize: function (attrs, options) {
this.bind("change:text", function (model, val) {
console.log(model.previous("text"), val);
});
}
});
var blog = Blog({text: "sample"});
blog.set({text: "example"}); // => sample example
前回の値を使ったバリデーションは validate
で行うことができるので、こちらは主にログ管理などに使うことになるでしょう。
##change([options])
小ネタになるのですが、例えば for ループの中で set
を複数回に分けて呼び出して、少しずつ属性を設定するような場面があったら、毎回 change を発生させるのではなく、最後に一回だけ発生させたくなるでしょう。その時、最後の set
でだけ silent を false
にするのでもいいですが、change
を使うと簡単にできます。
// 何かの事情で model.set({a: 1, b: 1, c: 1, d: 1}) と書けない
model.set({a: 1}, {silent: true});
model.set({b: 1}, {silent: true});
model.set({c: 1}, {silent: true});
model.set({d: 1}, {silent: true});
model.change(); // 最後に一回だけ change イベントを発生
change
の代わりに trigger('change')
でいいのではないかと思うかも知れませんが、ダメです。理由はソース参照
#データの取得と永続化
WEB サーバやブラウザの localStorage からデータを取得してモデルを作ったり、逆にそれらにモデルを保存する方法です。
##id
モデルの主キー。主キーとわざわざ読んでいるのは、この値を使ってアクセスする API の URI を変更し、通常用いられる RESTful API では、この値がデータベース中での主キーと対応する為です。
##urlRoot
次の url
中で用いられる。
##url()
モデルのリソースの URI を返す。id
と urlRoot
を組み合わせて、/urlRoot/id
とするのがデフォルト。
var Blog = Backbone.Model.extend({
urlRoot: '/api/blogs'
});
var blog = new Blog({id: 1});
blog.url() // => /api/blogs/1
実際に url
自体を直接利用する機会は無いと思います。これは、後述する fetch
save
destroy
の中で利用されます。
##fetch([options])
model.url()
にアクセスして帰ってくる JSON を使ってモデルを更新します。例えば、次のようなモデルオブジェクトがあるとします。
blog.url() // => /api/blogs/1
blog.get('text') // => undefined
そして、/api/blogs/1
にアクセスすると次のような JSON が返ってくる場合
{"text": "foobar", "datetime": "December 14, 2011 01:36:00"}
fetch
を実行すると次のようになります。
blog.fetch()
blog.get('text') // => foobar
blog.get('datetime') // => December 14, 2011 01:36:00
options
の値が内部では jQuery.ajax
に渡されるので、/api/blogs/1?foo=bar
のようにクエリ文字を渡したい場合は次のように書きます。
blog.fetch({data: {foo: "bar"}});
また、fetch
した時は change イベントが発生します。抑制するには options
に silent です。
##parse(response)
さて先ほどの例だと datetime という属性も作られますが、現状では文字列のままです。Date
型のオブジェクトにしたい場合のように、サーバからの返り値を属性に設定するまえに処理したい場合には parse
を使いましょう。
var Blog = Backbone.Model.extend({
urlRoot: "/api/blogs",
parse: function (response) {
response.datetime = new Date(response.datetime);
return resopnse;
}
});
blog = new Blog({id: 1});
blog.fetch();
blog.get("datetime"); // => Date オブジェクト
##save([attrs], [options])、destroy([options])
url
で差される先にデータを保存及び削除します。当然ですが、サーバが RESTful インタフェースを実装している必要があります。Rails で実装する方法については RailsアプリでBackbone.jsを使う で取り上げました。
#データのバリデーション
##validate(attributes)
set
と save
で silent が false であるときに実行されます。
var Blog = Backbone.Model.extend({
initialize: function (attrs, options) {
},
validate: function (attrs) {
if (attrs.text.length === 0) {
return "本文が入力されていません";
}
}
});
返り値が会った場合、error イベントが発生します。
これを捕捉するため、bind
を使ってもよいですが、特定の状況下でのみ処理を行いたいような場合は、次のように options
として error ハンドラを渡すと簡単です。
blog.bind("error", function (model, error) {
console.log(error);
});
blog.set({text: ""}) // => 本文が入力されていません
blog.set({text: ""}, {
error: function (model, error) {
console.log('hoge');
}
}); // => hoge
このように、options
で error ハンドラが渡された場合、trigger('error')
は実行されないので注意が必要です。
#まとめ
基本的な Model の使い方を見てきました。
次回は、前回取り上げた View
と Model
の連携に関する話題です。
それではまた明日。