Studyplusのweb版を担当していた久保です。 最近はRailsを触ったりしています。
今回は社内向けの管理画面を作る際に、どうしても動的にDOMを操作する必要があったのでjQueryの代わりにVue.jsを導入してみました。
なぜVue.jsを選んだのか
- Railsが生成したhtmlをテンプレートとして使うことができる
- Rails5系以降であれば、webpackerを利用するだけで良いので導入が楽
導入方法
導入時の環境は以下
- rails: 5.2.0
- ruby: ruby 2.5.1p57
- node: v8.11.2
- yarn: 1.7.0
- webpacker: v3.5.3
webpacker
gem 'webpacker'
$ bundle install
$ bundle exec rails webpacker:install
Vue.js
$ bundle exec rails webpacker:install:vue
Vue.jsを動かす
webpacker.yml
に記載されていますが、標準だと、app/javascript/packs
配下のファイルがエントリーファイルです。
JavaScriptファイルの読み込み
例えば app/javascript/packs/hello-world.js
とした場合、ヘルパーメソッドを使って簡単に読み込むことができます。
... <%= javascript_pack_tag 'hello-world', 'data-turbolinks-track': 'reload', defer: true %> </head> ...
今回は app/javascript/components
配下にVue.jsのコンポーネントを何個か定義して、エントリーファイル側でimportする形で実装しました。
開発環境で動かす
bin/webpack-dev-server
というファイルができており、これを使うと JavaScript を編集した際にビルドが走りブラウザが勝手にリロードされるようになります。
web: rails s -p 3000 client: sh -c 'rm -rf public/packs/* || true && bin/webpack-dev-server'
みたいなファイルを用意して、foreman を追加し
$ bundle exec foreman start -f Procfile.dev-server
とすると良いかもしれません。
本番環境
細かいチューニングが必要な場合は webpack の設定を弄るべきかもしれませんが、特に何もしなくても assets-precompile 時にいい感じになります。ここが本当に楽で素晴らしいと思っています。
Railsで生成したフォームの変化を検知して動的にDOMを操作する
Vue.jsを採用した理由として
Railsが生成したhtmlをテンプレートとして使うことができる
と書きましたが、簡単にその機能の紹介をします。
【サンプル】select要素の変更に応じてcss classの付け外しを行う
以下が設定の一部とマウント対象のerbです。
const ConversionType = { el: '#js-conversion_type', data: { form: { conversionType: document.getElementsByClassName('js-conversion_type')[0].value } }, computed: { isRequired: function() { switch (this.form.conversionType) { case 'nop': return { title: false, urlIOS: false, urlAndroid: false, }; // 以下case文省略 } } } } export default ConversionType;
<div id="js-conversion_type"> <div class="row mb-4"> <div class="col-xs-3"> <%= form.label :conversion_type %> </div> <div class="col-xs-9"> <%= form.select :conversion_type, MessageDraft.enum_for_selectbox(:conversion_type), {}, class: 'form-control js-conversion_type', 'v-model' => 'form.conversionType' %> </div> </div> <div class="row mb-4"> <div class="col-xs-3"> <%= form.label :button_title, 'v-bind:class' => '{required: isRequired.title}' %> </div> <div class="col-xs-9"> <%= form.text_field :button_title, id: :message_draft_button_title, class: 'form-control', 'v-bind:disabled' => '!isRequired.title' %> </div> </div> <div class="row mb-4"> <div class="col-xs-3"> <%= form.label :button_url_ios, 'v-bind:class' => '{required: isRequired.urlIOS}' %> </div> <div class="col-xs-9"> <%= form.text_field :button_url_ios, id: :message_draft_button_url_ios, class: 'form-control', 'v-bind:disabled' => '!isRequired.urlIOS' %> </div> </div> <div class="row mb-4"> <div class="col-xs-3"> <%= form.label :button_url_android, 'v-bind:class' => '{required: isRequired.urlAndroid}' %> </div> <div class="col-xs-9"> <%= form.text_field :button_url_android, id: :message_draft_button_url_android, class: 'form-control', 'v-bind:disabled' => '!isRequired.urlAndroid' %> </div> </div> </div>
ConversionType.jsの説明
el: '#js-conversion_type'
に関してですが、 公式ドキュメント を読むと
既存の DOM 要素に Vue インスタンスを与えます。
render 関数または template オプションも存在しない場合、マウントしている DOM 要素にある HTML がテンプレートとして抽出されます。
と書いてあります。この設定により、_form.html.erb 配下を Vue.js のテンプレートとして扱うことが可能になります。
data: { form: { conversionType: document.getElementsByClassName('js-conversion_type')[0].value } },
ドキュメントは こちら です。 select要素の初期値が後述する v-model を用いた場合に設定されなかったため、値を設定しています。
select要素の変更に合わせて、class等を操作する
v-model ディレクティブ と、computed プロパティ を用います。
<%= form.select :conversion_type, MessageDraft.enum_for_selectbox(:conversion_type), {}, class: 'form-control js-conversion_type', 'v-model' => 'form.conversionType' %>
と設定するだけで、onChange 等を書かずに値の変更を検知できます。
更に computed プロパティを用いて form.conversionType
の値に応じて各 input 要素が必要かどうかの bool値を持つオブジェクトが算出されるように設定します。
computed: { isRequired: function() { switch (this.form.conversionType) { case 'nop': return { title: false, urlIOS: false, urlAndroid: false, }; // 以下case文省略 } } }
テンプレート側からは
<div class="row mb-4"> <div class="col-xs-3"> <%# isRequired.title が true の場合は required クラスが付与される %> <%= form.label :button_title, 'v-bind:class' => '{required: isRequired.title}' %> </div> <div class="col-xs-9"> <%# isRequired.title が false の場合は disabled 属性が付与される %> <%= form.text_field :button_title, id: :message_draft_button_title, class: 'form-control', 'v-bind:disabled' => '!isRequired.title' %> </div> </div>
というふうに参照できます。
まとめ
Vue.js全く触ったことなかったのですが、あくまでRailsメインで画面を組んだ時に、補助としてVue.jsを使うのは導入と学習コストの点から悪くないのではという印象を受けました。