この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちわ。深澤です。
今回はボタンを押したらJSONデータを取得してページの内容を書き換える簡単でAjaxなサンプルからSpineを知っていこうと思います。SpineにはContactsやTodos等のサンプルアプリもありますが、わりと量が多く、理解するのに自分も時間がかかったため、簡単なところから始めてみます。
動作するサンプル
まずは動くものを見てください。ボタンを押すと通信してHTMLを書き換えます。
処理の流れのイメージ
1.Controllerがイベントを受け取る
2.ControllerがModelにデータを取得させる
3.ControllerがViewにModelで処理した結果を反映させる
Controllerが主体となってデータ担当のModelと描画担当のViewに仕事をさせる。といったイメージです。
サーバーサイドのMVCフレームワークと概念は一緒です。データをDBから取得するのかXMLHttpRequestで取得するのかの違いくらいです。
パッケージ構成
まずはパッケージ構成です。hemで自動生成された構成をそのまま使っていますが、MVCがそれぞれmodels,views,controllersパッケージになっているのでどのファイルがどんな役割かがかわかりやすいと思います。
その他パッケージ/ファイルは今回は編集しません。
今回更新したファイル
apps/ models/animal.coffee views/items.eco controllers/animals.coffee index.coffee public index.html data.json
HTML
自動生成されているindex.htmlにControllerを紐付けるdiv#animalSectionとJSONを取得するボタンを追加します。
HTMLももちろんMVCでいうViewになります。
public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>App</title>
<link rel="stylesheet" href="/application.css" type="text/css" charset="utf-8">
<script src="/application.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
var jQuery = require("jqueryify");
var exports = this;
jQuery(function(){
var App = require("index");
exports.app = new App({el: $("body")});
});
</script>
</head>
<body>
<!-- 以下追加 -->
<h2 id="toc-">動物の一覧が見たいな</h2>
<div id="animalSection">
<input type="button" id="showAnimals" value="Show Animal list">
</div>
<!-- ここまで -->
</body>
</html>
Model
Modelはどんなプロパティを持っているか、それをどうやって取得するか等を定義します。
spineコマンドでmodels下にTESTクラスと一緒にある程度自動生成してくれます。
$ spine model animal
app/models/animal.coffee
# 必要なモジュールの読み込み
Spine = require("spine")
class Animal extends Spine.Model
# モデルの先頭で設定する モデル名、モデルの持つプロパティ1、モデルの持つプロパティ2…
@configure "Animal", "name"
# Ajaxクラスを継承
@extend Spine.Model.Ajax
# URLを指定
@url: "/data.json"
module.exports = Animal
Controller
全てのControllerは生成されたHTMLの1つのエレメントに関連付けられます。 最初のspine app コマンドで自動生成されるApp Controller(index.coffee)はindex.htmlのscript部分を見るとbodyタグに関連付けられています。App Controllerの配下に子供のControllerを作っていくような作りを推奨してるようなので、これはこのままにして新たにControllerを作ります。SpineではControllerはModelの複数形の名前になるようです。ということでModelのクラス名がAnimalなのでAnimals(ファイル名はanimals)とします。こちらも自動生成さるとcontrollers下に生成されます。
$ spine controller animals
ControllerのAnimalsを#animalSectionに関連付けます。これはApp Controllerに定義します。
app/index.coffee
require('lib/setup')
Spine = require('spine')
Animals = require('controllers/animals')
# Controllerクラスを継承
class App extends Spine.Controller
# 最初に呼び出されるメソッド
constructor: ->
# 親クラスのconstructorを呼び出す
super
# Animals Controllerのインスタンスを生成 #animalsSection に紐付ける
@animals = new Animals(el: $('#animalSection'))
module.exports = App
app/controllers/animals.coffee
Spine = require('spine')
Animal = require('models/animal')
class Animals extends Spine.Controller
className: 'animals'
# イベント定義
events:
'click #showAnimals':'animalsClick'
# ボタンをクリックされた時のハンドラ
# JSONデータを取得して表示する
animalsClick: (event) ->
# データ取得
Animal.fetch()
# データが取得された時の処理
Animal.bind "refresh", (data) =>
# データをitemsに設定
@items = data[0]
# 描画
@render()
# 描画する
render: ->
@html require('views/items')(@items)
module.exports = Animals
View
Viewはテンプレートエンジンを使います。デフォルトではecoになっています。これはRubyでいうERBやJavaで言うJSPのCoffeeScript版です。
@はJavaScriptでいうthisですが、ここではAnimalインスタンスを指します。そのanimalオブジェクトにAminals でitemsとしたデータが渡されます。(何処で頭文字の小文字のanimalオブジェクトが生成された?)
app/views/items.eco
<ul>
<% for item in @animal: %>
<li><%= item.name %></li>
<% end %>
</ul>
JSONデータ
使用したJSONデータです。
public/data.json
[JavaScript] { "animal": [ { "name": "Cat" }, { "name": "Dog" }, { "name": "Okapi" } ] } [/JavaScript]
まとめ
小さいサンプルですが、素のjQuery/JavaScriptで書くのと比べ、クラスで役割を分割させた簡潔なルックスのコードになっていると思います。疎結合なためにTESTもしやすいはずです。最初にこのフレームワークを覚える必要はありますが、ある程度の規模を超えた時にカオスなコードになるのをだいぶ防げそうな雰囲気は感じられたかと思います。業務的なアプリや、ゲームやその要素を取り入れたアプリでも活躍しそうです。
次回からは公式ドキュメントに沿ってClassやController等の詳細を見ていきます。
今回使用したソースはGitHubで公開しています。
git://github.com/fukasawa-takeshi/spine-sample.git
でわでわ