後へ      Topへ      次へ

Stimulus: JavaScript制御

モデル共通コード:オプション の中級欄で触れましたが、
@step_options が期待する動作をしない件を改善します。

具体的には、
book 編集画面で Step を変更する際、
階層(Floor) → 棚(Cabinet) → 段(Step)、の順番に選ぶことで、
分かりやすく確実に Step が選べるようにします。
具体的な流れは、以下の通りです。

  1. Floor を選択すると
    その Floor に属する Cabinet のみの選択肢が現れる
  2. Cabinet を選択すると
    その Cabinet に属する Step のみの選択肢が現れる
  3. Step を選択する

なお、

Step 編集画面で Cabinet を選択する際も、
Floor → Cabinet で上記と同様の動きをします。
ほぼ同じなので、制御用の JavaScript コードも共用しています。

参考リポジトリ: https://github.com/Bonv-dev/book_mgmt/commit/a7e8840
\app\javascript\controllers\select_place_controller.js
\app\views\books\_form.html.erb

全体の流れ

先に、動作全体の流れを書いておきます。
新規用語が満載ですが、それぞれ後述しますのでご安心ください。

  1. HTML (Stimulus)
    1. ユーザーがプルダウンメニューの選択肢を変更する
    2. Stimulus の action により、JavaScript が呼ばれる
  2. JavaScript (Stimulus)
    1. target のデータをチェックして、選択肢の状態を把握
    2. コントローラを呼ぶのに必要な url を生成
    3. fetch(url, { headers: { Accept: “text/vnd.turbo-stream.html” } })
      により、コントローラが呼ばれる
  3. コントローラ (Turbo Stream)
    1. 選択肢のデータを揃える
    2. respond_to - format.turbo_stream で
      (アクション名).turbo_stream.erb が読み込まれる
  4. turbo_stream.erb (Turbo Stream)
    1. turbo-stream タグの target ID に対して action を実施
    2. 結果が HTML の文字列になる。
    3. fetch した JavaScript に文字列を返す
  5. JavaScript (Turbo Stream)
    1. Turbo.renderStreamMessage(html) で画面を書き換える

Stimulus

Stimulus は Rails が標準搭載している GEM で、
JavaScript の制御を担当します。

概ね、以下の事ができます。

素の JavaScript を書くより簡単に処理できます。

本章でも、サンプルプログラムを例に説明します。

Stimulus は、
上記の流れの中で、
Floor や Cabinet を選択したイベントを捉えて、
「選択肢を再構成する」コントローラのコードを呼ぶ部分を担当します。

generate stimulus

まず、JavaScript のファイルを生成します。
ruby bin/rails generate stimulus select_place
を実行し、
\app\javascript\controllers\select_place_controller.js
が生成されます。

中身は

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="select-place"
export default class extends Controller {
  connect() {
  }
}

です。

data-controller

View ファイルに対して、Stimulus で制御する範囲を指定します。
上記の select_place_controller.js に書かれている通り、
制御する範囲を data-controller=”select-place” で囲みます。

e.g.
<div data-controller="select-place">
  制御範囲
</div>

JavaScript のファイル名の先頭部分を取り出し、
アンダーバー(_) をハイフン(-) に変換したものを
data-controller に指定します。

一つのファイルに複数の data-controller を置くことも可能です。

value

HTML のデータ(文字列や数値) を JavaScript に渡す際に使用します。

e.g.
HTML側:
<div 
   data-controller="select-place"
   data-select-place-is-step-form-exist-value="true"
>

JavaScript側:
export default class extends Controller {
  static values = { isStepFormExist: Boolean }
}

HTML側は、data-controller を定義した場所に、
"data-"+"data-controllerの名前-"+"valueのラベル(Kebab Case)"+"-value"="設定値"
で、
JavaScript側 は、static values に
valueのラベル(Lower Camel Case): 型
の形式で記載します。

JavaScript 内で実際に使用する時は、
"this."+"valueのラベル"+"Value"
の形式で記載します。
this.isStepFormExistValue のようになります。

ファイル名など Rails 側は Snake Case、
HTML 側は Kebab Case、
JavaScript 側は Lower Camel Case、
と、場所によって異なるので注意。

 

target

HTML の要素を制御する際に使用します。

e.g.
HTML側:
<%= select_tag "book[step_id]", 
  (途中省略)
  data: { select_place_target: "step" }
%>

JavaScript側:
static targets = ["step"]

JavaScript 内で実際に使用する時は、
"this."+"targetのラベル"+"Target"
の形式で記載します。
this.stepTarget のようになります。
this.stepTarget.value.length===0 といった形で使用します。

対象の要素が複数の場合は、  
this.stepTargets のように末尾が複数形になります。

 

action

HTML 上でのユーザー操作をキャッチして、
JavaScript を動作させたい際に使用します。

e.g.
HTML側:
<%= form.submit "Update Book", data: { action: "click->select-place#checkParams" } %>

JavaScript側:
checkParams(event) {
  # 何らかの動作
}

click-> なので、
“Update Book” ボタンが押された時、
submit が実行される前に JavaScript の checkParams が実行されます。
submit ができない状態だったら、
event.preventDefault() でキャンセルする、
といった用途で使えます。

action は、
HTML 側も Lower Camel Case (Kebab Case じゃない) ので注意。
HTML に記述しているものの、click-> の中は JavaScript の世界なので。

他にも、
select で選択した時とか、
文字を入力した時とか、
様々なイベントで使えます。

 

Accept: “text/vnd.turbo-stream.html”

Stimulus の target で、選択肢が変わった事を検知した後、
この状態をコントローラに伝える必要があります。

画面の書き換えは JavaScript でも可能ですが、
選択肢をどう絞り込むかなどのデータを予め持っておく必要があったり、
選択肢の書き換えの煩雑さを考えると避けたいところです。
そこで、
処理をコントローラに依頼して、
書き換えるべき HTML を生成してもらいます。

具体的なコード部分は以下の通りです。

fetch(url, {
  headers: {
    Accept: "text/vnd.turbo-stream.html"
  }
})
  .then(response => response.text())
  .then(html => {
    Turbo.renderStreamMessage(html)
})

url には、アクセス先の URL と、どの要素のIDが選ばれているかを入れています。
headers に必要なパラメータを入れ、
fetch で url のコントローラ・アクションコードを呼びます。

コントローラは、画面更新のためのデータを用意します。
ただ、Accept パラメータに text/vnd.turbo-stream.html を指定しているため、
実際に画面を書く(render する)のではなく、
Turbo Stream を使って、書き換える部分の HTML を生成。

今回コントローラを呼んだのは JavaScript なので、
生成した HTML を画面に表示するのではなく、JavaScript に送り返します。

Turbo Stream については、次章で説明します。

コントローラ側が受け取ったデータは、
request で確認できます。
headers の Accept なら、
request.headers["Accept"]
です。

コントローラで処理した結果(画面を書き換える箇所の HTML)が
response で帰ってくるので、
response.text() でテキスト部分を取り出し、
Turbo.renderStreamMessage(html) でHTMLを書き換えます。


後へ      Topへ      次へ