岡山Ruby会議の懇親会で、Ember.jsがいいよ。これからbackbone.jsをやるくらいだったら、Ember.jsのほうをオススメするよと言われていたので、Ember.jsを試してみようと思ってやってみた(みている)。
ちなみに私はEmber.jsの事前知識はほとんどないので注意。
参考にしたURLはこちら。
Rails + Ember.js
Railsプロジェクト作成
まずは、railsプロジェクトを作る。
rails new EmberRails --database=sqlite3 -T
Gemfileに以下を追加。
gem 'ember-rails'
gem 'ember-source', '1.0.0.rc6.2'
bundle install したあと、ember用のテンプレートを作る。
bundle install
rails g ember:bootstrap -g --javascript-engine coffee
config/environments/development.rbに以下を追加する。
config.ember.variant = :development
config.handlebars.precompile = false
とりあえず、Todoでscaffoldしてみようと思う。
また、/用のコントローラーも作る。
rails g scaffold todo content:string closed:boolean
rails g controller home index
bundle exec rake db:migrate
scaffoldすると、Ember.js用のファイルも生成される。
Ember.jsで使われるtodoのmodelも自動で作られる。
# for more details see: http://emberjs.com/guides/models/defining-models/
EmberRails.Todo = DS.Model.extend
content: DS.attr 'string'
closed: DS.attr 'boolean'
ember-railsならば、最初からRestでアクセスするようになっているので、
store.js.coffeeは編集せず。
Ember.js用ファイルを編集
ルーティング
router.js.coffeeを編集して、ルートを教える。
# For more information see: http://emberjs.com/guides/routing/
EmberRails.Router.map ->
@resource('todos', ->
@resource('todo', {path: ':todo_id'})
)
Todosのrouteも別ファイルに定義する。
EmberRails.TodosRoute = Ember.Route.extend({
model: ->
EmberRails.Todo.find()
})
ここで、Home#indexがrootになるように、ルーティングを修正する。
EmberRails::Application.routes.draw do
root to: "home#index"
resources :todos
end
事前データ登録
表示するデータが欲しいので事前に登録しておく。
rails c
適当に作ろう。
Todo.create(content: 'テスト1')
Todo.create(content: 'テスト2')
Todo.create(content: 'テスト3')
Todo.create(content: 'テスト4')
Todo.create(content: 'テスト5')
サーバ起動
Webrickを起動する。
rails s
テンプレート作成
さて、Ember.jsでのテンプレートエンジンは、handlebars.jsである。
このテンプレートを全部読み込むようにするために、templatesの下に、all.jsを作る。
//= require_tree .
Ember.jsのテンプレートを使う場合、デフォルトでapplication.hbsがlayoutファイルになるようである。
先にこれを作っておく。
<header id="header">
<h2>{{#linkTo "index"}}HOME{{/linkTo}}</h2>
<nav>
<ul>
<li>{{#linkTo "todos"}}Todos{{/linkTo}}</li>
</ul>
</nav>
</header>
<div id="content">
{{outlet}}
</div>
{{outlet}}に、サブのViewが表示される模様。
リンクのTodosをクリックしたときに表示されるViewを定義する。
(ルーティングとかはまだだけど先に作る。)
<h1>Todos</h1>
<ul>
{{#each todo in controller}}
<li>{{todo.content}}</li>
{{else}}
<li>まだありません。</li>
{{/each}}
</ul>
{{outlet}}
表示してみる
http://localhost:3000 にアクセスしてみる。
Todosのリンクを押してみる。
データの登録
データの登録は、Ember.jsのコントローラー経由で行える。
View側
まずはViewを修正する。
Ember.TextFieldなどを使うとよい。
登録ボタンについては、Ember.Buttonがあったので使ってみたところ、非推奨になっていた。
{{action "hoge"}}を使ってねということらしい。これをbuttonと組み合わせる。
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする?" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each todo in controller}}
<li>{{todo.content}}</li>
{{else}}
<li>まだありません。</li>
{{/each}}
</ul>
{{outlet}}
Controller側
createTodoアクションを、コントローラーに作成する。コントローラーはTodosController。
View側で設定したvalueBindingから値を取得する。
何もデータがなければ登録しないようにしてある。
EmberRails.TodosController = Ember.ArrayController.extend
createTodo: ->
content = this.get('newContent')
return unless (content.trim())
todo = EmberRails.Todo.createRecord({
content: content
})
this.set('newContent', '')
todo.save()
これで、登録するボタンを押したら登録される。
ポチッとな。
追加された。
データの削除
データの削除も登録と同じように、コントローラー経由で行う。
ただし、新規登録のときとは違い、ArrayControllerを継承したコントローラーではなく、ObjectControllerを継承したコントローラーに依頼する必要がある。
Controller側
というわけでTodoControllerを作成する。
メソッドにremoveTodoを定義。
EmberRails.TodoController = Ember.ObjectController.extend
removeTodo: ->
todo = this.get('model')
todo.deleteRecord()
todo.save()
View側
次にView側。eachで todo in controller とやっていたのだが、これだとうまくいかなくなった。
each controller だと、モデルオブジェクトが省略されて、いきなりメンバ変数にアクセスできるっぽいのだが、メンバ変数名をcontentにしていたらダメだった。content.contentにしないと表示されない(これは俺がTodoモデルを作ったときのカラム名の失敗か…)。
省略されたmodelに紐づくコントローラーをitemControllerで指定するみたいである。
というわけで、以下のように削除ボタンを追加した。
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする?" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each controller itemController="todo"}}
<li>
{{content.content}}
<button {{action "removeTodo"}} class="destroy">削除</button>
</li>
{{else}}
<li>まだありません。</li>
{{/each}}
</ul>
{{outlet}}
これで、いけるはず。
新しいタスクの削除ボタンをポチッとな。
はい、消えました。
データの更新
Todoをダブルクリックしたらテキストエリアにするようにしてみます。
View側
Todoの状態が変わったら表示を変える設定を、Viewに設定します。
bindAttrで、Todoの状態が変わったら(編集中になったら)liのclassが変わるようにする。
また、isEditingの状態が変わったら表示するViewを変えるようにする。
Todoの内容をダブルクリックしたら、TodoController#editTodoを呼び出すようにする。
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする?" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each controller itemController="todo"}}
<li {{bindAttr class="isCompleted:complated isEditing:editing"}}>
{{#if isEditing}}
<input type="text" class="editing">
{{else}}
<label {{action "editTodo" on="doubleClick"}}>{{content.content}}</label>
<button {{action "removeTodo"}} class="destroy">削除</button>
{{/if}}
</li>
{{else}}
<li>まだありません。</li>
{{/each}}
</ul>
{{outlet}}
Controller側
コントローラーにTodoの編集中という状態を持たせる。
ダブルクリックでeditTodoが呼ばれると、編集中に変わるようにする。
EmberRails.TodoController = Ember.ObjectController.extend
isEditing: false
removeTodo: ->
todo = this.get('model')
todo.deleteRecord()
todo.save()
editTodo: ->
this.set('isEditing', true)
テキストエリアになった。オブジェクトの状態を見張る処理がテンプレート内に定義されている。
再びView側
このままだと更新できないので、Viewを変更する。
テンプレートからViewを呼び出す。Viewの定義はviews以下にすることができる。
valueの値を既存のデータをbindしてあるので編集するときに反映される。
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする?" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each controller itemController="todo"}}
<li {{bindAttr class="isCompleted:complated isEditing:editing"}}>
{{#if isEditing}}
{{view EmberRails.EditTodoView valueBinding="content.content"}}
{{else}}
<label {{action "editTodo" on="doubleClick"}}>{{content.content}}</label>
<button {{action "removeTodo"}} class="destroy">削除</button>
{{/if}}
</li>
{{else}}
<li>まだありません。</li>
{{/each}}
</ul>
{{outlet}}
呼び出されるViewの内容はこれ。Ember.TextFieldを継承してあり、処理が終わるとTodoControllerのacceptChengesメソッドを呼び出す。
EmberRails.EditTodoView = Ember.TextField.extend
classNames: ['edit']
insertNewLine: ->
this.get('controller').acceptChanges()
focusOut: ->
this.get('controller').acceptChanges()
didInsertElement: ->
this.$().focus()
再びController側
コントローラーでは、acceptChangesが呼ばれたときの処理を実装するだけ。
ここで更新処理を実装する。
編集が終わったのでisEditingをfalseに戻し、modelを保存している。
modelが保存されたら自動的にputメソッドで更新処理がサーバに送られる。
EmberRails.TodoController = Ember.ObjectController.extend
isEditing: false
removeTodo: ->
todo = this.get('model')
todo.deleteRecord()
todo.save()
editTodo: ->
this.set('isEditing', true)
acceptChanges: ->
this.set('isEditing', false)
this.get('model').save()
ダブルクリックすると、
元のデータを反映しつつ、
編集完了。
やってみて
なんとなく、本当になんとなくだが、backbone-railsよりはember-railsのほうがわかりやすい気がする。
画面側はEmber.jsでcoffee書きながらバリバリやっていって、
最初はRestAdapterではなく、FixtureAdapterでモックを作るようにして、
サーバ側が実装されたらRestAdapterに置き換える、というやり方で分業ができるんかなーと
思った。まぁFixtureAdapter試してないんだが・・・。