Mithril は名前を聞いたことがある程度で、特に触ってみる予定はなかったのですが、Shibu's Diary: 世界最速でMithril本をリリースした話 を見て俄然興味が湧いたので、早速、黒ムツ本 を購入して読みました。
コンパクトで大変読みやすかったです。オススメ。コードもMITライセンスで公開 されていて嬉しい。
あえてRailsで
本を読んでなんとなく理解できたところで、次は実際に触ってみるフェーズ。
今後いろいろといじっていくにあたり、サーバサイドは使い慣れているRailsでやりたい。
ということで、まずはTodoのサンプルをRails化してみました。
全コードはこちらにあります。
https://github.com/tnantoka/my-mithril
やったこと
Mithrilを入れる
mrsweaters/mithril-rails は古かった1し、今回はMSX を使う予定もなかったので、rails-assets で入れました。
source 'https://rails-assets.org' do
gem 'rails-assets-mithril'
end
$ bundle
//= require mithril
コンポーネントを書く
app/assets/javascripts/components/todo.coffee
に置きました。
モデル
class Todo
constructor: (data = {}) ->
@description = m.prop(data.description || '')
@done = m.prop(false)
@list: ->
m.request(method: 'GET', url: '/todos', type: Todo, initialValue: [])
@save: (list) ->
data =
todos: list.filter (t) -> !t.done()
MyMithril.request(method: 'POST', url: '/todos', data: data)
MyMithril.request
はcsrf-token
を追加してm.request
を呼ぶ自作関数です。https://github.com/tnantoka/my-mithril/blob/master/app/assets/javascripts/my-mithril.coffee
mithril-rails使えば勝手にやってくれる?(未確認)
ビューモデル
Enterキーで保存できるようにしています。
vm =
init: ->
vm.list = Todo.list()
vm.description = m.prop('')
vm.entered = m.prop(false)
add: ->
if vm.description().length
todo = new Todo(description: vm.description())
vm.list().push(todo)
vm.description('')
Todo.save(vm.list())
toggle: (value) ->
@done(value)
Todo.save(vm.list())
onkeyup: (value) ->
vm.description(value) unless vm.entered()
vm.entered(false)
onkeypress: (e) ->
if e.keyCode == 13
vm.entered(true)
vm.add()
else
m.redraw.strategy('none')
コントローラー
controller = ->
vm.init()
ビュー
onchangeはfocusがある間は発生しない(忘れててちょっとハマった)ので、onkeyupで入力中のキーワードをvm.descriptionに設定しています。
view = ->
m 'div', [
m '.row', [
m '.col-sm-4', [
m '.input-group', [
m 'input.form-control', {
value: vm.description()
onkeyup: m.withAttr('value', vm.onkeyup)
onkeypress: vm.onkeypress
}
m 'span.input-group-btn', m 'button.btn.btn-primary', onclick: vm.add, 'Add'
]
]
]
m 'div', vm.list().map (t) ->
textDecoraton = if t.done() then 'line-through' else 'none'
m '.checkbox', [
m 'label', [
m 'input[type=checkbox]', onclick: m.withAttr('checked', vm.toggle.bind(t)), value: t.done()
m 'span', { style: { textDecoration: textDecoraton } }, t.description()
]
]
]
コンポーネントを呼び出す
手抜きでViewから呼んでます。
javascript:
m.mount($('#app').get(0), components.todo.component);
サーバーサイドを実装する
createでは受け取ったデータをsessionに突っ込んで、indexではそれをそのまま返しています。
class TodosController < ApplicationController
def index
render json: session[:todos].to_a
end
def create
session[:todos] = params[:todos]
render nothing: true
end
end
テストを書く
それなりに長いので割愛。
https://github.com/tnantoka/my-mithril/blob/master/spec/javascripts/todo_spec.coffee にあります。
実行には、Teaspoon を使いました。README通りやればCoverageもちゃんと取れてよかったです。
Sinon.JSのserver.respondImmediately = true
がなぜか効かなかった(?)り、npm module使えるの?とかまだわかってないところだらけですが。
動いた!
http://my-mithril.bornneet.com/ で動いています。
tweet-boxの方はReact.js Introduction For People Who Know Just Enough jQuery To Get By · React for Designers の劣化コピーです。(Rails関係ない)
感想
まだ触り始めたばかりですが、vue.js ぐらい手軽に利用できて、しかもrouter2 も付いてきて、設計指針も示してくれる(これは本が親切なのもありますが)ので、片手間フロントエンダーには大変ありがたいと思いました。
だいぶ慣れてきたので、次は何か大きめのものを作ってみる予定。