TurboでのJavaScriptの利用を支援するStimulus
StimulusはTurboの機能を補完する位置付けのライブラリです。Turboは便利で高機能ですが、より細かな振る舞いを実装するには、やはりJavaScriptのコーディングが必要になってきます。このようなときにStimulusは一定の枠組みを導入してくれますので、JavaScriptの濫用という起きがちな問題を回避できるというメリットがあります。
ここからは、Stimulusを使ってアプリケーションに入力検証機能を付加していきます。検証機能はRails自身が備えていますが、これをフォームの送信前に行ってみようというわけです。検証内容は「フィールドが埋められているか?」とし、フィールドを埋めないと[Create new Word]ボタンをクリックできないものとします(図6)。
Stimulusの基本的な動作
Stimulusは、既存のHTMLに対しての処理を主な目的としています。対象となるHTML要素は「ターゲット」として指定し、処理の内容を「アクション」に定義します。両者は「コントローラ」によってひも付けます。Hotwireを有効にしてアプリケーションを作成すると、app/javascript/controllersフォルダに、Stimulusのコントローラファイルhello_controller.jsがリストのように作成されています。命名規則はRailsと同様で、Helloコントローラのためのファイルということになります。
import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { this.element.textContent = "Hello World!" } }
ここにはコールバック関数connect()が定義されているだけです。ただ、コントローラファイルはあっても、ターゲットがない場合には、何の動きも発生しません。そこで、アプリケーションの一覧ページに、以下のようにdiv要素を追加します。
<h1>Words</h1> <div data-controller="hello"></div> 追加 <div id="words"> …略…
ここで一覧ページを表示させると、図7のように「Hello World!」と表示されるのが分かります。これは、hello_controller.jsのconnect関数の処理内容そのものです。
ここでconnectとは、HTML要素とコントローラがひも付いた(接続された)時点で呼ばれることを意味しています。index.html.erbに追加したdiv要素にはdata-controller属性が付加されており、これがdiv要素とHelloコントローラをひも付けているのです。同様にdata-target属性、data-action属性がありますが、これらについてはサンプルを紹介しながら随時取り上げることにします。
ここで、index.html.erbに追加したdiv要素は不要になるので、削除するかコメントアウトして無効にしておいてください。
コントローラを作成する
stimulus-railsによって、Stimulusのコントローラはrails generate stimulusコマンドで作成できます。今回は入力内容の検証なので、Validateコントローラとします。
% rails generate stimulus validate create app/javascript/controllers/validate_controller.js
これで、app/javascript/controllers/validate_controller.jsが作成されます。このファイルには、Stimulusコントローラに必要な基本的な記述がすでに行われています。なお、RailsのデフォルトではImport Mapsが使われますので、app/javascript/controllers以下のスクリプトは自動的にインポートされます。
コントローラを設定する
コントローラを設定します。フォームの検証になるので、app/views/words/_form.html.erbのform_withメソッドにコントローラのためのdata属性を指定します。
<%= form_with(model: word, data: { controller: 'validate' }) do |form| %> …略…
この指定により、form要素に「data-controller: 'validate'」属性が付加されて、フォーム内でValidateコントローラが使用できるようになります。
ターゲットとアクションを設定する
操作の対象となるターゲット(コントローラから参照する際の名前)と、必要な場合にはアクション(コントローラのメソッド名)を設定します。操作とは、更新はもちろんのこと取得も対象になるので、ボタンと2つのテキスト入力フィールドをターゲットに設定します。同じくapp/views/words/_form.html.erbの3つのコントロールにターゲットのdata属性を設定します。
…略… <div> <%= form.label :japanese, style: "display: block" %> <%= form.text_field :japanese, data: { validate_target: 'japanese', action: 'input->validate#japanese' } %> (1) </div> <div> <%= form.label :english, style: "display: block" %> <%= form.text_field :english, data: { validate_target: 'english', action: 'input->validate#english' } %> (2) </div> <div> <%= form.submit data: { validate_target: 'button'} %> (3) </div>
設定内容をまとめると表のようになります。
フォーム要素 | ターゲット | アクション |
---|---|---|
(1)テキスト入力フィールド(japanese) | japanese | input->validate#japanese |
(2)テキスト入力フィールド(english) | english | input->validate#english |
(3)ボタン | button | ― |
「data: { validate_target: 'japanese'…」の指定で、data-validate-target属性が指定され、validateコントローラにjapaneseターゲットが設定されます。また、「action: 'input->validate#japanese'」の指定でdata-action属性が指定され、フィールド変更時(input)にvalidate#japaneseアクションを呼び出すことを指定します。これらの設定により、コントローラ内でテキスト入力フィールドとボタンの参照が可能になります。
アクションを記述する
最後はアクションです。ここまでは、ヘルパーメソッドにオプションを指定していくだけで必要な設定が行えましたが、アクションの中身はJavaScriptのコーディングが必要です。とはいえ、あらかじめ決められたルールの中でコーディングすればよいのです。
…略… export default class extends Controller { static targets = [ "button", "japanese", "english"] (1) static values = { valid: true, japanese: '', english: '' } (2) connect() { (3) this.japaneseValue = this.japaneseTarget.value (4) this.englishValue = this.englishTarget.value this.validValue = this.japaneseValue != '' && this.englishValue != '' (5) } japanese() { (6) this.japaneseValue = this.japaneseTarget.value this.validValue = this.japaneseValue != '' && this.englishValue != '' } english() { (7) this.englishValue = this.englishTarget.value this.validValue = this.japaneseValue != '' && this.englishValue != '' } validValueChanged() { (8) this.buttonTarget.disabled = !this.validValue }
StimulusもRailsと同様に、設定より規約(CoC)となっています。そのため、非常に簡略化されたコーディングが可能です。(1)では、ターゲットのリストを配列で設定しておきます。HTML側のdata-validate-target属性で指定する値と一致している必要があります。(2)では、コントローラが保持する変数のリストをハッシュ(キー、初期値)で設定しておきます。
(3)は既出の通りHTML要素とコントローラがひも付いたときに呼び出されるコールバック関数です。(4)で2つのテキスト入力フィールドから値を取り出し、それを(2)で宣言した変数に代入していますが、注目すべきはその書式です。ターゲット、変数ともに「this.xxxxTarget」「this.xxxxValue」の形式で参照できます。
(5)は、2つのテキスト入力フィールドが空でないかを保持する変数への代入です。
(6)と(7)は、data-action属性で指定されたアクションの本体です。それぞれ、テキスト入力フィールドから値を取り出し、(5)と同様に設定を行っています。
最後の(8)は、Value APIという非常に便利な機能のための関数です。「xxxxValueChanged」という形式で関数を命名すると、対応する変数xxxxの値が変化すると呼び出されるコールバック関数になります。これにより、ボタンに対して有効/無効を設定する箇所を1つに絞れるというわけです。
まとめ
今回は、前回に続きHotwrireについて、自由自在な書き換えができるTurbo Streamsと、より洗練されたJavaScriptの利用を支援するStimulusを紹介しました。次回は、JSONベースのWebサービスを開発するRuby on Rails APIを紹介します。