5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

opal-knockout

導入

gemのインストール

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

Gemfile
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ライブラリの読込方法が異なります。

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

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

opal0.8系
require 'opal'
require 'knockoutjs'
require 'opal-knockout'
require_tree '.'

使い方(サンプル)

ViewModelの作成

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

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です。真新しいものはありません。

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

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_modelwith: userに該当するようにViewModelを追加します。

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楽しいです。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?