gem opal-knockoutを作ったので使い方を解説する


OpalというRubyをJavascriptにコンパイルするgemがあることを最近知りました。

[Opal - Ruby to Javascript Compiler](http://opalrb.org/)

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が書けたら嬉しいなぁと思っていたので、ラッパーライブラリを作ってみました。といっても、自分がよく使う部分しか実装していませんが（開発者募集中）

[opal-knockout](https://github.com/patorash/opal-knockout)

## 導入

### gemのインストール

opal-knockoutと、Knockout.jsが必要なので、rails-assetsを使って取得します。

```ruby:Gemfile
gem 'opal-knockout'

source 'https://rails-assets.org' do
  gem 'rails-assets-knockoutjs', '3.3.0'
end
```

```bash
bundle install
```

### application.rb(application.jsの代わり)

opalを入れてると、application.jsの代わりにapplication.rbを書けます。

たしか、Opal 0.7系と0.8系でJavascriptライブラリの読込方法が異なります。

```ruby:opal0.7系
#= require opal
#= require knockoutjs
#= require opal-knockout
#= require_tree .
#= require_self
```

0.8系はたしかこんな感じ…（覚えてません）

```ruby:opal0.8系
require 'opal'
require 'knockoutjs'
require 'opal-knockout'
require_tree '.'
```

## 使い方（サンプル）

### ViewModelの作成

Ruby風に表現したかったので、observableの定義をattr_observableと書くだけでできるようにしました。observableArrayやcomputedも同様です。computedはblockでも書けるようにしました。

```ruby:user_view_model.rb
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です。真新しいものはありません。

```ruby:user.rb
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`のようにします。個人的には、メソッドを呼び出しているのが一目でわかるのでいいと思います。

```erb:index.html.erb
<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>
      &nbsp;
      <span data-bind="text: last_name"></span>
    </li>
  </ul>
</div>
```

### 実際にバインドする

RootViewModelのインスタンスを作って、これを`Knockout.apply_bindings`に渡します。これでバインドされます。インスタンス作成時にHash形式で渡すか、`add_view_model`で`with: user`に該当するようにViewModelを追加します。

```ruby:application.rb
#= 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楽しいです。