むらたです。
若手ではないのですが、こちらに出張投稿します。
AcroquestではHTML5+CSS3+JavaScriptによるグラフィカルなWebUIを開発するためのOSSであるWGPを開発しています。このWGPを利用する上で、JavaScriptMVCフレームワークを活用したいと考えており、私はJavaScriptMVCフレームワークを調査しています。そんなわけで「ステートフルJavaScript」を読んでおり、Spine.jsを調査しています。
Spine.jsはbackbone.jsにインスパイアされて開発されたCoffeeScriptと親和性が高いフレームワークであり、CoffeeScript好きな私としてはbackbone.jsの前にこちらを調べています。今回はSpine.jsのTodoアプリケーションを作ってみたので、その内容をまとめました。
Spine.jsのTodoアプリケーションを作る
Spine.jsではExampleのページでこのTodoアプリケーションが紹介されています。画面イメージはこちら。
Spine.jsにはSpine.appという生成機能があるのですが、本家のサイトで紹介されている例では、Spine.appを使わない方法(一番シンプルな方法)で説明されているので、ここではSpine.jsの売りでもあるSpine.appを使った開発方法を紹介します。
動作する本家のサンプルはこちらです。
LiveDemo
開発環境の準備
まずは開発環境を作ります。Spine.jsにて手順が説明されていますので、簡略版です。開発には、node.js、Spine.app、Hemが必要になります。node.jsのインストールについては省略しますw
npm install spine.app hem
gオプションでグローバルインストールするのが一般的ですが、私は嫌いなので、ローカルにインストールしてます。
spine.app、hemをインスト−ルしたら、アプリケーションのひな形を作成します。
./node_modules/spine.app/bin/spin app todos create todos/.npmignore create todos/app create todos/app/controllers create todos/app/controllers/.gitkeep create todos/app/index.coffee create todos/app/lib create todos/app/lib/setup.coffee create todos/app/models create todos/app/models/.gitkeep create todos/app/views create todos/app/views/.gitkeep create todos/css create todos/css/index.styl create todos/css/mixin.styl create todos/package.json create todos/Procfile create todos/public create todos/public/favicon.ico create todos/public/index.html create todos/slug.json create todos/test create todos/test/public create todos/test/public/index.html create todos/test/public/lib create todos/test/public/lib/jasmine.css create todos/test/public/lib/jasmine.html.js create todos/test/public/lib/jasmine.js create todos/test/specs create todos/test/specs/.gitkeep cd todos npm install .
npm installで必要なライブラリがインストールされます。
npm install jquery.tmpl --save
今回はjquery.tmplを使うので、こちらもインストールします。これで開発環境の準備は完了です。
Modelの作成
それでは、Spine.appを使ってModelを作成し、そのコードを書きます。
../node_modules/spine.app/bin/spine model task
これでModelのひな形が app/models/task.coffee として生成されます。このひな形にコードを書いていきます。
app/models/task.coffee
Spine = require('spine') class Task extends Spine.Model @configure 'Task', "name", "done" @extend @Local @active: -> @select ( (item) -> !item.done ) @done: -> @select ( (item) -> !!item.done ) @destroyDone: -> rec.destroy() for rec in @done() module.exports = Task
Spine.jsでは何も指定しないとデータはメモリ上に保存されますが、ここでは@Localを使ってHTML5のローカルストレージを使います。Spine.Modelのselect関数はコールバックを引数に取るのですが、本家のサンプルの書き方では一目では意味が分からなかったので、分かり易さのためにカッコをつけてます。(CoffeeScript的にはなくてもよい)
Controllerの作成
次にControllerを作成します。Modelと同じくSpine.appを使ってひな形を生成します。
../node_modules/spin.app/bin/spin controller tasks
Controllerは app/controllers/tasks.coffee として生成されます。このひな形にまたコードを書いて行きます。
app/controllers/tasks.coffee
Spine = require('spine') Task = require('models/task') $ = Spine.$ class Tasks extends Spine.Controller constructor: -> super @item.bind("update", @render) @item.bind("destroy", @release) events: "change input[type=checkbox]": "toggle" "click .destroy": "remove" "dblclick .view": "edit" "keypress input[type=text]": "blurOnEnter" "blur input[type=text]": "close" elements: "input[type=text]": "input" render: => @replace($("#taskTemplate").tmpl(@item)) @ toggle: -> @item.done = !@item.done @item.save() remove: -> @item.destroy() edit: -> @el.addClass("editing") @input.focus() blurOnEnter: (e) -> if e.keyCode is 13 then e.target.blur() close: -> @el.removeClass("editing") @item.updateAttributes({name: @input.val()}) module.exports = Tasks
このControllerはTodoリストのViewに当たる部分をコントロールしています。Todoリストの一行を表現するテンプレートはjquery.tmplを使っているのですが、テンプレート部は後述するindex.htmlに記述しています。
次に、Todoアプリケーション全体の中で、Todoリスト部分以外のViewをコントロールするもう一つのControllerを作成します。これはアプリケーション全体も含めてTaskAppクラスとして作成します。このコードはSpine.appがアプリケーションのひな形を生成したときに作成される app/index.coffee に記述します。
app/index.coffee
require('lib/setup') Spine = require('spine') Task = require('models/task') Tasks = require('controllers/tasks') class TaskApp extends Spine.Controller constructor: -> super Task.bind("create", @addOne) Task.bind("refresh", @addAll) Task.bind("refresh change", @renderCount) Task.fetch() events: "submit form": "create" "click .clear": "clear" elements: ".items": "items" ".countVal": "count" ".clear": "clearlink" "form input": "input" addOne: (task) => view = new Tasks(item: task) @items.append(view.render().el) addAll: => Task.each(@addOne) create: (e) -> e.preventDefault() Task.create(name: @input.val()) @input.val("") clear: -> Task.destroyDone() renderCount: => active = Task.active().length @count.text(active) inactive = Task.done().length if inactive @clearlink.show() else @clearlink.hide() module.exports = TaskApp
参考にしている本家のサンプルに実はバグがあり、上記はそれを修正しています。(どこか分かりますか?)このバグを見つけるのに時間がかかってしまいましたよ(TT
Viewを作成する
今回のアプリケーションは非常にシンプルなものなので、Viewは最初に表示する public/index.html だけです。先ほど書いたように、この中にTodoリストのView部分となるテンプレートをscriptタグで書いています。
public/index.html
<!DOCTYPE html> <html> <head> <meta charset=utf-8> <title>Todos</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/x-jquery-tmpl" id="taskTemplate"> <div class="item {{if done}}done{{/if}}"> <div class="view" title="Double click to edit..."> <input type="checkbox" {{if done}}checked="checked"{{/if}}> <span>${name}</span> <a class="destroy"></a> </div> <div class="edit"> <input type="text" value="${name}"> </div> </div> </script> <script type="text/javascript" charset="utf-8"> var jQuery = require("jqueryify"); var exports = this; jQuery(function(){ var TaskApp = require("index"); exports.app = new TaskApp({el: $("#tasks")}); }); </script> </head> <body> <div id="views"> <div id="tasks"> <h1>Todos</h1> <form> <input type="text" placeholder="What needs to be done?"> </form> <div class="items"></div> <footer> <a class="clear">Clear completed</a> <div class="count"><span class="countVal"></span> left</div> </footer> </div> </div> </body> </html>
サンプルではHTML5の機能である chache.manifest を指定して、アプリケーションをキャッシュ化していますが、デバッグ時に面倒なので削除しています。
作成するものは以上です。
Build & 動作確認
Spine.appで生成したアプリケーションはHemを使っています。このHemはライブラリマネージャとしての機能を持っており、ライブラリの依存関係を解決してくれます。index.htmlを見ると分かるように、本家のサンプルに記載している数々のscriptタグによるライブラリの指定は、ここでは記載していません。これはHemによってビルドを行うと、今回開発したjavascript(CoffeeScript)と依存関係のあるjavascriptを全てapplication.jsという一つのjavascriptファイルに結合します。なので、index.htmlにはapplication.jsのscriptタグしかありません。
実はcssについても同様のことができるのですが、今回は本家のサンプルからapplication.cssをコピーしてきてそれを利用します。(これはHemによるビルドの後にやらないとapplication.cssがビルドの度に生成されるのでご注意を)
おっと、忘れてました。jquery.tmplを個別にinstallしているので、Hemの定義ファイルである slug.json に jquery.tmpl を追加する必要があります。
{ "dependencies": [ "es5-shimify", "json2ify", "jqueryify", "jquery.tmpl", "spine", "spine/lib/local", "spine/lib/ajax", "spine/lib/route", "spine/lib/manager" ], "libs": [] }
では、ビルドをしましょう。
./node_modules/hem/bin/hem build
これで全ての準備が完了です。public/application.js、application.cssが生成されます。動作の確認はhemに組み込まれているサーバ機能を使って試します。
./node_modules/hem/bin/hem server
これで localhost:9294 にアクセスすれば試すことができます。
実際にStep by Stepで試してみてください。
それでは!
補足編を書きました。
JavaScriptMVCフレームワーク「Spine.js」を使ってみる(補足編) - Taste of Tech Topics