Ruby on RailsとBackbone.js始めました。
Rails × Backbone.jsを試したくて調べて出てきた記事「RailsアプリでBackbone.jsを使う」を読み、まったく同じことをやろうとしたら色々ハマりました。
自分がRailsもBackbone.jsも初心者なために陥ったのが大部分だと思いますが、4年近い歳月の間に変わってしまったことも多くあったようです。
そこで、目指すところは元記事と同じまま手順を2015年版に書き換えてみたいと思います。
Backbone.jsの本家ドキュメントにtodoリストをブラウザのLocalStorageを使って保存するチュートリアルがあります。
todo.js
今回は、このtodoアプリのバックエンドとして、Ruby on Railsを使うように変更してみたいと思います。
Rails
rails new
〜rake db:migrate
まで。
$ rails new todos
$ cd todos
$ echo "gem 'rails-backbone'" >> Gemfile
$ bundle install
$ rails g backbone:install
$ rails g scaffold todo title:string done:boolean
$ rake db:migrate
元記事ではbackbone.jsと依存ライブラリをダウンロードしてapp/assets/javascripts
に入れていましたが、今回はrails-backboneをgemとして追加することにします。
- gemとして追加してやったほうが管理が楽
gem名はrails-backboneですがレポジトリ名はなぜかbackbone-railsです。ご注意ください。
- 元記事と同じやり方だとAjax時にRailsのCSRF対策に引っかかってしまう
Backbone.syncをハックして避ける方法もあるようですが同gemでも回避できるようなので。
[backbone][rails][CSRF][ajax]backbone syncでcsrfタグもpostする方法
Backbone.jsでRailsのCSFR防止tokenをうまく扱う
View Template
元のチュートリアルのhtmlのbodyタグ内をapp/views/todos/index.html.erbにコピペします。
元記事のとおりhtmlをコピペしましょう。
scriptの読み込みはapplication.jsのrequire
で行うようになっているので以下の記述は削除します。
<script src="spec/support/jquery.js"></script>
<script src="spec/support/underscore.js"></script>
<script src="spec/support/backbone.js"></script>
<script src="backbone.localStorage.js"></script>
<script src="todos.js"></script>
Separation
サンプルのTodoアプリではBackbone.jsで使うテンプレートをindex.html.erbに<script type="text/template">
として読み込んでいます。このままだとerbの制御文字とunderscore.jsが競合してparse errorになるので元記事ではunderscore.jsのロジックを書き換えています。
同じやり方でも構いませんがサーバサイドとクライアントサイドで使用するファイルは極力分けて管理したいので今回はテンプレートを外部ファイル化してみます。
まず、index.html.erb内のテンプレート記述2つ(以下)を削除します。
<!-- Templates -->
<script type="text/template" id="item-template">
<div class="view">
<input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
<label><%- title %></label>
<a class="destroy"></a>
</div>
<input class="edit" type="text" value="<%- title %>" />
</script>
<script type="text/template" id="stats-template">
<% if (done) { %>
<a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
<% } %>
<div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
</script>
次にapp/assets/javascripts/backbone/template
に以下の2ファイルを追加しましょう。
<div class="view">
<input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
<label><%- title %></label>
<a class="destroy"></a>
</div>
<input class="edit" type="text" value="<%- title %>" />
<% if (done) { %>
<a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
<% } %>
<div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
これらはjavascriptからそれぞれ以下のような形式で呼び出せます。
JST['backbone/templates/item-template']
JST['backbone/templates/stats-template']
テンプレートの分離はこれで完了です。
Backbone.js
app/assets/javascripts/backbone/todos.js.coffee
を削除し、本家のtodos.jsをコピーして配置します。
jstとして分離したテンプレートを使用するように変更します。
// The DOM element for a todo item...
var TodoView = Backbone.View.extend({
// Cache the template function for a single item.
// template: _.template($('#item-template').html()),
template: JST['backbone/templates/item-template']
(中略)
// Our overall **AppView** is the top-level piece of UI.
var AppView = Backbone.View.extend({
// Our template for the line of statistics at the bottom of the app.
// statsTemplate: _.template($('#stats-template').html()),
statsTemplate: JST['backbone/templates/stats-template']
これだけで動くようになりますが、Rails使っているのに今更JavaScriptって無いんだろうな…と思いCoffeeScript化してみました。長くなるのでGistに貼り付けておきました。
Design
jsと同様にcssも本家からコピーしてきます。
cssを自前でscssにするのは…まァいいか、という気持ちです。変換をブラウザで行ってくれる便利サイト(css2sass)がありますので本家のtodos.cssをコピーしてここでscssに変換し、デフォルトで作成されているapp/assets/stylesheets/todos.css.scss
に貼り付けましょう。
Todo削除アイコンdestroy.pngがそのままだと読み込めないので以下のように書き換えます。
#todo-list .destroy {
position: absolute;
right: 5px;
top: 20px;
display: none;
cursor: pointer;
width: 20px;
height: 20px;
/* background: url(destroy.png) no-repeat; */
background: url(//localtodos.com/destroy.png) no-repeat;
}
Completion!
これにて完了です。rails s
して動作確認しましょう。
Backbone.jsも引き続き学んでいくわけですが、このアプリを軸にMarionette.jsやらMongoDBやらも試していきたいと思っています。