#Backbone.js Advent Calendar 5日目
Backbone.jsの本家ドキュメントにtodoリストをブラウザのLocalStorageを使って保存するチュートリアルがあります。
今回は、このtodoアプリのバックエンドとして、Ruby on Railsを使うように変更してみたいと思います。
##Backbone.jsとサーバの通信
Backbone.jsはModelやCollectionの内容をサーバと同期するための手段を提供してくれています。標準で用意されているBackbone.syncはサーバがRESTfulと呼ばれるインタフェースを提供していることを前提に動作しますが、同期する方法を自作することも可能で、例えば上記のtodoリストのチュートリアルではLocalStorageにデータを保存するために、Storeという名前のオブジェクトを自作して用いています。そのため、Backbone.jsとサーバを同期する際は大きく分けて2通りの方法があることが分かります。
1.サーバにRESTfulなインタフェースを作り、標準のBackbone.syncを使う
2.独自のインタフェースを作り、独自の同期方法を実装する
Railsではscaffoldという機能を使うことで極めて簡単にRESTfulなAPIを作ることができるので、今回はこの機能を使ってみることにしましょう。
##Railsの用意
$ rails new todos
$ cd todos
$ rails g scaffold todo text:string done:boolean
$ rake db:migrate
あとはunderscore.jsとbackbone.jsをapp/assets/javascriptsに追加し、underscore.jsがbackbone.jsより先に読み込まれるように、app/assets/javascripts/application.jsを以下のように変えます。
//= require jquery
//= require underscore
//= require backbone
//= require todos
Rubyを一行も書いていませんが、なんとこれで終わりです!あとはhtmlとjavascriptをがりがりと書いていくだけです
##htmlとjavascriptを書く
元のチュートリアルのhtmlのbodyタグ内をapp/views/todos/index.html.erbにコピペします。このチュートリアルでは新しくTodoを作った時にDOMを作るのに、underscore.jsのテンプレート機能(とjQuery)を利用していますが、erbのロジックととunderscore.jsが標準で用意しているロジックが競合するため、そのままだとRailsがunderscore.jsのテンプレート用のロジックをerbのものだと勘違いしてエラーとなってしまいます。そこでtodos.jsに
_.templateSettings = {
interpolate: /\{\{(.+?)\}\}/g,
evaluate: /\{%(.+?)%\}/g,
escape: /\{%-(.+?)%\}/g
};
と記述して標準のロジックを変更してしまうことにします。上記の記述では、Python用WebフレームワークであるDjangoのテンプレートエンジンに似た記法を採用しています。そしてこの新しいロジックに合うようにコピペしたhtml内の二つの<script type="text/template">
タグの中身を以下のように変更します。
<script type="text/template" id="item-template">
<div class="todo {{ done ? 'done' : '' }}">
<div class="display">
<input class="check" type="checkbox" {{ done ? 'checked="checked"' : '' }} />
<div class="todo-text"></div>
<span class="todo-destroy"></span>
</div>
<div class="edit">
<input class="todo-input" type="text" value="" />
</div>
</div>
</script>
<script type="text/template" id="stats-template">
{% if (total) { %}
<span class="todo-count">
<span class="number">{{ remaining }}</span>
<span class="word">{{ remaining == 1 ? 'item' : 'items' }}</span> left.
</span>
{% } %}
{% if (done) { %}
<span class="todo-clear">
<a href="#">
Clear <span class="number-done">{{ done }}</span>
completed <span class="word-done">{{ done == 1 ? 'item' : 'items' }}</span>
</a>
</span>
{% } %}
</script>
最後にチュートリアルのjsをtodos.jsにコピペし、TodoList
コレクションのlocalStorageプロパティを削除し、代わりにurl
プロパティに"/todos"
をセットします。次のようになるはずです
window.TodoList = Backbone.Collection.extend({
model: Todo,
// localStorage: new Store("todos"), 削除
url: "/todos",
done: function () {
...
},
...
});
これで完成です。Railsを起動してアクセスしたら、動作していることが確認できるはずです。詳しい動作説明などは元のチュートリアルを参照してください。
このように、Railsのscaffold機能を用いることで、簡単にBackbone.jsのバックエンドを用意することができます。
##おまけ
Backbone.jsはRESTfulなAPIを対象とすると書きましたが、ある程度のカスタマイズを行うことができます。
まず、APIのURLのカスタマイズを行うことができ、上記のように、ModelやCollectionのurlプロパティまたはメソッドやurlRootプロパティでカスタマイズすることができます。
そして、サーバから返されるjsonは初期状態では以下のような形式を期待されていますが
[{text: "hoge", done: false}, {text: "bar", done: true}]
例えば、他に検索ヒット数やページャのページ数などを同時に返して、以下のような形式になる場合は
{info: {results: 100, total_pages: 10, current_page:1},
todos: [{text: "hoge", done: false}, {text: "bar", done: true}]}
parseメソッドを用いてカスタマイズすることができます。上記の例の場合なら
var TodoList = Backbone.Collection.extend({
...
parse: function (response) {
this.info = response.info; // ここでヒット件数の処理なども記述できる
return response.todos // 返される値がモデルにセットされる。Collectionだから配列を返している
}
});