satococoa's blog

主にサーバーサイド、Web 系エンジニアのブログです。Go, Ruby, React, GCP, ...etc.

Backbone.js + Sinatra + CoffeeScriptのサンプル

もう一つYokohama.rb 第9回でやっていたことをシェアします。
Backbone.jsの勉強をするため、Sinatra + Backbone.jsで簡単なサンプルを作っていました。
・・・しかもCoffeeScriptで。

Backbone.jsとは?

ここを読んでいる方は、おそらくサーバーサイドをPHPなりRubyなりで書きつつも、クライアントサイドをjQueryなどを使ってバリバリ書いていることと思います。

すると、ある程度のところまではjQueryの力もあって楽々書けるのですが、だんだん機能が増えるにしたがってサーバーとのAjaxなやりとりや、データの変更に伴うDOM操作などがあちこちに散在し、コードにだんだん穢れが溜まっていってしまいます。

そこで今回のBackbone.jsです。これの力を借りてクライアントサイドを整理することができます。
さらにRailsとの相性を考慮に入れて作られているようで、ドキュメントを見るとRailsとうまく協業するためのRails側の設定なども書かれていたりします。

少しまとめますと、BackBone.jsとは、クライアントサイドにMVCの考え方を持ち込んで開発を行うことができるライブラリです。下記エントリを読むと雰囲気がよくわかります。(と、丸投げ)

Backbone.jsを利用したクライアントサイドMVCの導入についてそろそろ書いておくか

上記エントリを読んでみてもわかるのですが、MVCとは言えサーバーサイドのMVCとはずいぶん違います。

作ったサンプルとそのソース

はじめにお断りしておきますが、今回はMVCのうちのC(コントローラ)を使っていません。
・・・というのも、Backbone.jsのControllerはフラグメント(URLの後ろの#以下)を使用したルーティングが役割です。
個人的にこのフラグメントをルーティングに使う考え方にいまいち馴染まなかったのと、それをやりたければjquery-pjaxを使えばいいのかなぁという漠然とした考えからです。(まだ検証していませんが。)

CoffeeScriptで書いていますので、views/script.coffeeが今回のメインです。
他のコードは適当にSinatraでDBを使わずにAPIを作ったのと、herokuでCoffeeScript + SCSS動かすためのもろもろなので今回は略します。

Model
Message = Backbone.Model.extend
  defaults:
    text: 'これはデフォルトのメッセージです。'

MessageStore = Backbone.Collection.extend
  model: Message
  url: '/messages'
messages = new MessageStore

こんな感じでモデルの定義です。デフォルト値の定義しか行っていないものですが。
もちろん独自のメソッドをMessageモデルに定義することもできますが、今回はやっていません。

Messageが、単一のインスタンス(このアプリだったら1行分のメッセージ)を扱うモデル、MessageStoreはそれをまとめて操作するためのコレクションクラスです。なお、このCollectionにはUnderscore.jsメソッドががっつり取り入れられていますのでeachとか、mapとかfind, sortByみたいな便利なメソッドが使えます。

あと、urlで指定したパスに対してモデルの生成や取得を行ったときにRESTFulなアクセスを行います。
Backbone.syncというメソッドで再定義できますが、デフォルトでは以下のような動作を行います。

create -> POST    /messages
read   -> GET     /messages[/id]
update -> PUT     /messages/id
delete -> DELETE  /messages/id
View

今回は二つのViewを定義しました。

  1. メッセージを司るView
  2. アプリケーション全体を司るView

以下のようになっています。

MessageView = Backbone.View.extend
  tagName: 'p'
  template: _.template 'メッセージ<%= id %>: <%= text %>'
  className: 'message-row'
  initialize: () ->
    _.bindAll @, 'render'
    @.model.view = @
    @.model.bind 'change', @.render
  render: () ->
    $(@.el).html @.template(@.model.toJSON())
    @

AppView = Backbone.View.extend
  el: $('#main')
  events:
    'click #send': 'createMessage'
  initialize: () ->
    _.bindAll @, 'createMessage', 'addOne', 'addAll'
    messages.bind 'add', @.addOne
    messages.bind 'refresh', @.addAll
  createMessage: (data) ->
    message = $('#message').val()
    $('#message').val ''
    messages.create text: message
  addOne: (message) ->
    view = new MessageView model: message
    $('#log').append view.render().el
  addAll: (messages) ->
    messages.each @.addOne

appview = new AppView

messages.fetch()

MessageViewのところで特徴的なのはtemplateと、renderでしょうか。Underscore.jsを組み込んでいますので、_.templateというメソッドでテンプレートを定義でき、@.templateメソッドでそれを使用することができます。

initializeではrenderメソッドが実行されたときにthis(CoffeeScript上では@と表記)がこのオブジェクト自体になるよう、_bindAllメソッドを使用しています。これもUnderscore.jsのメソッドですね。

AppViewでは単純に『送信』ボタンが押されたときにcreateMessageメソッドが実行されるようにしています。
あとはmessages(MessageStoreのインスタンス)に新しくメッセージが加わったときの処理と、初回読み込み時にサーバーから取得したデータを一気に書きだす処理です。

messages.bindでは、messages(MessageStoreのインスタンス)に新たに要素が加えられた時に実行される処理を定義しています。

最後にmessages.fetch()で現在サーバーにある全メッセージを取得しています。
fetch()の成功時にrefreshイベントが発行され、それによってaddAllメソッドが走ります。

参考にしたソース

公式のExamplesからリンクされていたTodo List applicationをかなり参考にして書きました。

ソース

まとめ

今回はサーバーから全メッセージの取得及び新しいメッセージの作成機能までしかつくっていないので、正直イマイチありがたみがわからないソースになってしまいました。
きっと更新とか削除の機能を実装すればきっとありがたみが分かるのかと思いますが、クライアント側をオブジェクト指向的かつイベントドリブンに整理して書けそうな感触は得たので、ここまででサンプルを完成としました。

今度はRails3.1やpjaxと組み合わせていじってみたいと思います。