Model はあらゆるJavaScriptアプリケーションの中核をなすもので、対話形式のデータとデータに関連するロジック、つまり変換、検査、プロパティ値計算、アクセス制御などを含みます。あなたは Backbone.Model クラスを継承して、ドメイン固有のメソッドを実装するだけでよく、データ操作に関わる基本的な機能は Model クラスが提供します。
以下のコード例は不自然ではありますが、Modelとカスタムメソッド定義の実例です。ここでは属性値をセットし、その属性値変化のイベントを発火してます。このコードを実行すると、sidebarがブラウザのウィンドウに追加されるので、実際にさわってみることができます。
var Sidebar = Backbone.Model.extend({
promptColor: function() {
var cssColor = prompt("Please enter a CSS color:");
this.set({color: cssColor});
}
});
window.sidebar = new Sidebar;
sidebar.on('change:color', function(model, color) {
$('#sidebar').css({background: color});
});
sidebar.set({color: 'white'});
sidebar.promptColor();
Backbone.Model.extend(properties, [classProperties]) 原文独自の Model クラスを作成するには Backbone.Model を継承し、インスタンスプロパティを記述します。またオプションで classProperties を渡すこともできます。classPropertiesはコンストラクタ関数のプロパティとなります。
extend は適切にプロトタイプチェーンを組み立てるので、もし望むならば extend で作成されたサブクラスをさらに継承するサブクラスを作成することもできます。
var Note = Backbone.Model.extend({
initialize: function() { ... },
author: function() { ... },
coordinates: function() { ... },
allowedToEdit: function(account) {
return true;
}
});
var PrivateNote = Note.extend({
allowedToEdit: function(account) {
return account.owns(this);
}
});
superに関する補足: JavaScriptではsuper - つまりプロトタイプチェーンにおける上位のオブジェクトで定義された同名の関数 - を呼び出す簡単な方法がありません。もしあなたがsetやsaveのようなコア関数をオーバーライドした上で、親クラスの実装の方を使いたい場合、以下の例のように明示的に関数を呼び出すしかありません。
var Note = Backbone.Model.extend({
set: function(attributes, options) {
Backbone.Model.prototype.set.call(this, attributes, options);
...
}
});
new Model([attributes]) 原文モデルのインスタンスを生成する際に、引数 attributes で属性の初期値を指定することができます。モデルの属性は後からsetメソッドでも指定できます。もし initialize 関数が定義されていれば、モデル生成時に呼び出されます。
new Book({
title: "One Thousand and One Nights",
author: "Scheherazade"
});
まれなケースでは、風変わりですが、 constructor をオーバーライドして、モデルの実際のコンストラクタ関数を書き換えるということもできます。
model.get(attribute) 原文モデルの属性の現時点での値を取得します。例:note.get("title")
model.set(attributes, [options]) 原文モデルの(ひとつ、もしくは複数の)属性の値をセットします。いずれかの属性によりモデルの状態が変化した場合、"change"イベントが発行されます。オプション引数として{silent: true}を指定することでchangeイベントを抑止できます。特定の属性に対してのみchangeイベントを発行することも可能で、change:titleやchange:contentというふうに記述します。また、キーと値を別々に引数として渡す事も可能です。
note.set({title: "March 20", content: "In his eyes she eclipses..."});
book.set("title", "A Scandal in Bohemia");
モデルがvalidateメソッドを持つ場合、属性値がセットされる前に検査が実行されます。もし検査に失敗すれば何も変更されず、 set はfalseを返します。検査に成功すれば、 set はモデルへの参照を返します。オプション引数にerrorコールバック関数を渡す事もできます。その場合、検査に失敗すれば"error"イベントが発行される代わりにコールバック関数が呼ばれます。オプション引数に{silent: true}を指定することで、検査をスキップすることができます。
model.escape(attribute) 原文このメソッドはgetと似ていますが、HTMLエスケープされた属性値を返すという点で異なります。モデルから得たデータをHTMLに書き込む場合、 escape 経由で属性値を取得することでXSS攻撃を防ぐことができます。
var hacker = new Backbone.Model({
name: "<script>alert('xss')</script>"
});
alert(hacker.escape('name'));
model.has(attribute) 原文属性に値がセットされている場合、つまり属性値がnullでもundefinedでもない場合、trueを返します。
if (note.has("title")) {
...
}
model.unset(attribute, [options]) 原文属性を削除します。属性はモデル内部のハッシュから削除されます。オプションにsilentが指定されない限り、"change"イベントが発火します。
model.clear([options]) 原文モデルのすべての属性を削除します。silentプロパティが指定されない限り、"change"イベントが発火します。
model.id 原文id はモデルの特別な属性で、任意の文字列(数値やUUID)が指定可能です。属性に id を指定した場合、直接モデルのプロパティとしてコピーされます。モデルはコレクション内でidで特定可能です。また、デフォルトではモデルのURLを生成するためにidが使われます。
model.idAttribute 原文モデルはid属性のもとで一意に特定されますが、CouchDBやMongoDB等、異なる名前のキーを使用するバックエンドに接続する場合、それらのキーとid属性を透過的にマッピングするために、idAttribute属性をセットしてください。
var Meal = Backbone.Model.extend({
idAttribute: "_id"
});
var cake = new Meal({ _id: 1, name: "Cake" });
alert("Cake id: " + cake.id);
model.cid 原文cid (クライアントid)はモデルの特別な属性で、モデルが最初に作成されるときに自動的に付与されます。モデルがまだサーバに保存されておらず、最終的な id を持たない状況において、クライアントidを使うと便利です。クライアントidはc1, c2, c3...のような値をとります。
model.attributes 原文attributes はモデルの状態を保持した内部的なハッシュです。属性を更新するには、attributesを直接変更するのではなく、setを使ってください。また、属性値のコピーを取得したい場合はtoJSONを使ってください。
model.changed 原文changed 属性は最後に"change"イベントが発行されて以来変更されたすべての属性を含む内部ハッシュです。setメソッドとchangeイベントにより内部状態が保たれているため、 changed を直接変更しないでください。 changed のコピーはchangedAttributesにより取得できます。
model.defaults or model.defaults() 原文defaults ハッシュ(もしくは関数)はモデルのデフォルト属性を指定するために使用されます。モデルのインスタンスを生成する際に、値が明示的に指定されなかった属性はこのデフォルト値がセットされます。
var Meal = Backbone.Model.extend({
defaults: {
"appetizer": "caesar salad",
"entree": "ravioli",
"dessert": "cheesecake"
}
});
alert("Dessert will be " + (new Meal).get('dessert'));
JavaScriptではオブジェクトは参照として渡されます。つまり、オブジェクトをデフォルト値として指定した場合、それはすべてのインスタンスにより共有されます。
model.toJSON() 原文JSON.stringifyから呼び出され、モデルのattributes属性のコピーを返します。このメソッドはViewに渡す前に属性値を永続化/シリアライズするために使用されます。実際にはJSON文字列を返すわけではないので、メソッド名はいささか混乱を招くかもしれませんが、JavaScriptのtoJSONとJSON.stringifyもそのように動作します。
var artist = new Backbone.Model({
firstName: "Wassily",
lastName: "Kandinsky"
});
artist.set({birthday: "December 16, 1866"});
alert(JSON.stringify(artist));
model.fetch([options]) 原文サーバから取得したデータでモデルの状態をリセットします。処理はBackbone.syncへ委譲されます。jqXHRオブジェクトを返します。モデルがまだ初期化されていない場合や、サーバの最新状態を反映したい場合に使います。サーバから取得したデータが現在の属性値と異なる場合、"change"イベントが発行されます。オプションとしてsuccessおよびerrorコールバックを受け取ります。それぞれのコールバックの引数は順に(model, response, options)および(model, xhr, options)となります。
// Poll every 10 seconds to keep the channel model up-to-date.
setInterval(function() {
channel.fetch();
}, 10000);
model.save([attributes], [options]) 原文モデルをデータベース(もしくは他の永続データレイヤー)に保存します。処理はBackbone.syncへ委譲されます。検査に成功した場合はjqXHRオブジェクトを返し、それ以外はfalseを返します。引数の attributes ハッシュは、(setと同様)変更対象の属性を含みます。ここに含まれていないキーは変更の対象になりませんが、サーバへは完全なリソースが送られます。setと同様、ハッシュの代わりにキーと値を別々に引数として渡す事も可能です。もしモデルがvalidateメソッドを持っており、検査に失敗した場合、モデルは保存されません。もしモデルがisNewでtrueを返す場合、saveメソッドは"create"(HTTP POST)となり、もしモデルがサーバにすでに存在している場合、"update"(HTTP PUT)となります。
save呼び出しにより新しい属性が渡されると、"change"イベントが即座に発行されます。そしてサーバが変更を正常に反映した後に"sync"イベントが発行されます。モデルに新しい属性をセットする前にサーバをの応答を待つ場合は{wait: true}を渡します。
以下の例では、オーバーライドされたBackbone.syncがどのようにリクエストを受けるか着目してください。初回のモデル保存では"create"を、2回目は"update"のリクエストを受けます。
Backbone.sync = function(method, model) {
alert(method + ": " + JSON.stringify(model));
model.id = 1;
};
var book = new Backbone.Model({
title: "The Rough Riders",
author: "Theodore Roosevelt"
});
book.save();
book.save({author: "Teddy"});
save はオプションとしてsucessおよびerrorコールバックを受け取ります。それぞれのコールバックの引数は順に(model, response, options)および(model, xhr, options)となります。モデルがvalidateメソッドを持っていて、検査に失敗したときにもerrorコールバックが呼ばれます。サーバ側で失敗した場合は、200以外のHTTPレスポンスコードとともに、エラーがテキストもしくはJSONの形式で返却されます。
book.save("author", "F.D.R.", {error: function(){ ... }});
model.destroy([options]) 原文サーバにあるモデルを削除します。処理はHTTP DELETEリクエストとしてBackbone.syncへ委譲されます。成功した場合はjqXHRオブジェクトを返します。モデルがisNewでtrueを返す状態である場合、このメソッドはfalseを返します。オプションとしてsucessおよびerrorコールバックを受け取ります。それぞれのコールバックの引数は順に(model, response, options)および(model, xhr, options)となります。このメソッドを呼び出すことにより"destroy"イベントが発行され、モデルを包含するすべてのコレクションに対して下から上に通知されます。そしてサーバが削除を正常に反映した後に"sync"イベントが発行されます。モデルをコレクションから削除する前にサーバをの応答を待つ場合は{wait: true}を渡します。
book.destroy({success: function(model, response) {
...
}});
model.validate(attributes) 原文このメソッドは未定義ですが、もしJavaScriptで実行可能な検査をお持ちでしたら、ご自身でオーバーライドして独自の検査ロジックを記述することをお勧めします。 validate はsetとsaveが実行される直前に呼び出され、setもしくはsaveにより更新された属性値が引数として渡されます。もしこれら属性値が正しければ validate メソッドは戻り値として何も返さないでください。もし属性値が不正な場合、ご自身で定義したエラー値を返してください。単純にエラーメッセージの文字列を返すだけでもかまいませんし、プログラム的にエラーを記述した完全なエラーオブジェクトを返す事も可能です。もし validate がエラーを返した場合、setおよびsaveは続行されず、モデルの属性は変更されません。検査に失敗した場合は"error"イベントが発行されます。
var Chapter = Backbone.Model.extend({
validate: function(attrs) {
if (attrs.end < attrs.start) {
return "can't end before it starts";
}
}
});
var one = new Chapter({
title : "Chapter One: The Beginning"
});
one.on("error", function(model, error) {
alert(model.get("title") + " " + error);
});
one.set({
start: 15,
end: 10
});
"error"イベントは、モデルもしくはコレクションのレベルでのざっくりとしたエラーメッセージを提供するのに便利ですが、直接errorコールバックを指定する事で特定のエラーをより適切に扱うことができます。errorコールバックを指定することで処理がオーバーライドされ、”error”イベントは発生しなくなります。
account.set({access: "unlimited"}, {
error: function(model, error) {
alert(error);
}
});
model.isValid() 原文明示的でない方法で変更された場合、モデルはinvalidな状態になります。(もっともそのような方法はformの入力を扱うときに便利なのですが。)モデルが現在validかどうかvalidate関数をもとにチェックするには、model.isValid()をお使いください。
model.url() 原文サーバ上のモデルのリソースの相対URLを返します。モデルが別の場所に格納されている場合、このメソッドを正しい実装でオーバーライドしてください。"/[collection.url]/[id]"という形式のURLが生成されます。モデルがコレクションの一部でない場合は"/[urlRoot]/id"となります。
URL生成時にCollection#urlが呼ばれるので適切に実装してください。もしくはクラス内のすべてのモデルが共通のルートURLを持つ場合はurlRootプロパティを定義してください。例えばid属性が101のモデルがあったとします。このモデルが"/documents/7/notes"というurlでBackbone.Collectionに格納されている場合、このモデルのURLは"/documents/7/notes/101"となります。
model.urlRoot or model.urlRoot() 原文モデルがコレクションに属さない場合、urlRootを指定してください。これにより、デフォルトのurl関数はモデルのidをもとに"/[urlRoot]/id"のようなURLを生成するようになります。
urlRootは関数としても定義可能です。
var Book = Backbone.Model.extend({urlRoot : '/books'});
var solaris = new Book({id: "1083-lem-solaris"});
alert(solaris.url());
model.parse(response) 原文fetchやsaveの結果、サーバからモデルのデータを受け取った際に、 parse が呼び出されます。この関数は生のresponseオブジェクトを引数として受け取り、モデルにsetで渡す属性値を戻り値として返します。デフォルトの実装は何もしません。つまり単に受け取ったJSONレスポンスをそのまま返します。既存のAPIやネームスペースに合わせるためにレスポンスを処理する必要がある場合は、この関数をオーバーライドしてください。
バックエンドにRailsを使う場合、Railsのto_jsonのデフォルト実装はモデル名の配下に属性が含まれるので、Backbone全体でこの動作を無効にするには以下のように設定してください。
ActiveRecord::Base.include_root_in_json = false
model.clone() 原文このモデルと全く同じ属性値を持つインスタンスを新たに作成して返します。
model.isNew() 原文モデルがまだ一度もサーバに保存された事がないかどうか、を返します。モデルがまだidを持っていない場合はnewと見なされます。
model.change() 原文手動で"change"イベント、もしくは変更された各属性に対して"change:attribute"イベントを発行するための関数です。setメソッドに{silent: true}を渡して、モデルに対する急な変更をいったんとりまとめて、すべての変更が完了した時点でmodel.change()を呼び出す、という使い方をします。
model.hasChanged([attribute]) 原文最後に"change"イベントが発行されて以来、モデルが変更されたかどうかを返します。引数 attribute が渡された場合、指定された属性が変更されたか場合にのみtrueを返します。
このメソッドと後続のchange関連のメソッドは、"change"イベントの処理中にのみ有用です。
book.on("change", function() {
if (book.hasChanged("title")) {
...
}
});
model.changedAttributes([attributes]) 原文モデルの変更された属性のみを返します。オプション引数として attributes を外から渡す事もできます。その場合は、渡された属性のうち、モデルと値が異なるもののみ返却します。ビューのどの部分を更新すべきか調べる場合や、変更をサーバと同期するためにどのような関数呼び出しが必要か調べる場合にこのメソッドが使えます。
model.previous(attribute) 原文"change"イベントの処理中にこのメソッドを使う事で、属性の変更前の値を取得できます。
var bill = new Backbone.Model({
name: "Bill Smith"
});
bill.on("change:name", function(model, name) {
alert("Changed name from " + bill.previous("name") + " to " + name);
});
bill.set({name : "Bill Jones"});
model.previousAttributes() 原文モデルの変更前の属性値のコピーを返します。モデルのバージョン間の差分を取得する際や、エラー発生時に正常な状態に復旧する場合に使えます。