OpalというRubyをJavascriptにコンパイルするgemがあることを最近知りました。
Opal - Ruby to Javascript Compiler
RailsやってるとJavascriptだけはCoffeescriptを使うなどで対応しているけれどやっぱりちょっと違うしなーと思っていたのですが、これがあれば全部Rubyで書けるんじゃないのか?と思って興味を持った次第です。しかし実際のところは、JavascriptはわかってるんだけれどRubyで書きたいっていう人向けです(Coffeescriptもそうだけど)。
しかし、RubyでJavascriptが書けるといっても、表現しづらいところは直接Javascriptを実行したり、ラッパークラスを作って対応する必要があります。opal-jqueryというjQueryのラッパーライブラリがあるのですが、これもjQueryをRubyらしく表現するためのやつです。
RubyでもKnockout.jsしたい
私はJavascriptのMVVMはKnockout.js推しで、これがないともはや開発がしんどいと思っているのですが、Ruby風にKnockout.jsのViewModelが書けたら嬉しいなぁと思っていたので、ラッパーライブラリを作ってみました。といっても、自分がよく使う部分しか実装していませんが(開発者募集中)
導入
gemのインストール
opal-knockoutと、Knockout.jsが必要なので、rails-assetsを使って取得します。
gem 'opal-knockout'
source 'https://rails-assets.org' do
gem 'rails-assets-knockoutjs', '3.3.0'
end
bundle install
application.rb(application.jsの代わり)
opalを入れてると、application.jsの代わりにapplication.rbを書けます。
たしか、Opal 0.7系と0.8系でJavascriptライブラリの読込方法が異なります。
#= require opal
#= require knockoutjs
#= require opal-knockout
#= require_tree .
#= require_self
0.8系はたしかこんな感じ…(覚えてません)
require 'opal'
require 'knockoutjs'
require 'opal-knockout'
require_tree '.'
使い方(サンプル)
ViewModelの作成
Ruby風に表現したかったので、observableの定義をattr_observableと書くだけでできるようにしました。observableArrayやcomputedも同様です。computedはblockでも書けるようにしました。
class UserViewModel < Knockout::ViewModel
attr_observable :first_name, :last_name
attr_observable_array :users
attr_computed :full_name, :show_full_name
# blockでも書ける
attr_computed :hello do
"こんにちは、#{self.last_name.to_s}です。"
end
def initialize(first_name=nil, last_name=nil)
self.first_name = first_name
self.last_name = last_name
end
def add_user
self.users.push(User.new(self.first_name.to_s, self.last_name.to_s))
clear_form
end
private
def show_full_name
"#{first_name.to_s} #{last_name.to_s}"
end
def clear_form
self.first_name = ''
self.last_name = ''
end
end
Userクラスの作成
これはModelです。真新しいものはありません。
class User
attr_accessor :first_name, :last_name
def initialize(first_name, last_name)
self.first_name = first_name
self.last_name = last_name
end
end
Viewの作成
UserViewModelをバインドするViewを定義します。
Opalで作ったメソッドは、Javascript側で呼び出すときに、$
マークがメソッド名の前に付くので、clickイベントで呼び出すメソッドの指定には、$add_user
のようにします。個人的には、メソッドを呼び出しているのが一目でわかるのでいいと思います。
<div data-bind="with: user">
<form>
<label>First name: </label>
<input type="text" data-bind="textInput: first_name">
<br>
<label>Last name: </label>
<input type="text" data-bind="textInput: last_name">
<p data-bind="text: full_name"></p>
<p data-bind="text: hello"></p>
<button type="button" data-bind="click: $add_user">
追加
</button>
</form>
<ul data-bind="foreach: users">
<li>
<span data-bind="text: first_name"></span>
<span data-bind="text: last_name"></span>
</li>
</ul>
</div>
実際にバインドする
RootViewModelのインスタンスを作って、これをKnockout.apply_bindings
に渡します。これでバインドされます。インスタンス作成時にHash形式で渡すか、add_view_model
でwith: user
に該当するようにViewModelを追加します。
#= require opal
#= require knockoutjs
#= require opal-knockout
#= require_tree .
#= require_self
$root_view_model = Knockout::RootViewModel.new(
user: UserViewModel.new('Taro', 'Yamada')
)
# もしくは、
# $root_view_model = Knockout::RootViewModel.new
# $root_view_model.add_view_model(:user, UserViewModel.new('Taro', 'Yamada'))
Knockout.apply_bindings($root_view_model)
これで、RubyだけでKnockout.jsが使えるようになりました!(一部機能だけど)
まとめ
OpalとKnockout.js楽しいです。