🦓

[Rails]turboのカスタムアクションを使ってみる

2023/09/09に公開

はじめに

Turboは通常、7つの標準的なアクションがあります。
これらのアクションを使用することで、リアルタイムなページ更新や操作を実現できます。

  1. prepend: Turboフレーム内の特定の要素の前に新しい要素を追加します。
  2. append: Turboフレーム内の特定の要素の後ろに新しい要素を追加します。
  3. replace: Turboフレーム内の特定の要素を置き換えます。
  4. remove: Turboフレーム内の特定の要素を削除します。
  5. before: Turboフレームの前に新しいTurboフレームを挿入します。
  6. after: Turboフレームの後ろに新しいTurboフレームを挿入します。
  7. update: Turboフレーム内のコンテンツ全体を更新します。

https://github.com/hotwired/turbo/blob/main/src/core/streams/stream_actions.js

これらのアクション以外に、StreamsActionモジュールを使ってカスタムのアクションを作成することもできますのでいくつかの例を使って見ていきましょう。

ボタンのクリックに:

  1. 文字例をappendするアクション
  2. console.log()アクション
  3. ブラウザ通知アクション
  4. ページのリダイレクトアクション

こちら四つのアクションを作成していきます。

StreamActionsのソースコード

https://github.com/hotwired/turbo/blob/main/src/elements/stream_element.js

環境

Rails 7.0.7
ruby 3.2.1

文字例をappendする

投稿の一覧にボタンーをクリックしたら、文字Hello Railsを表示されるアクションを作ってみます。
turbo_streamフォーマットを有効にする必要があります。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    respond_to do |format|
       format.turbo_stream
       format.html
    end
  end
end

divの中に文字列を更新されるようにIDを用意します。
また、data: { turbo_stream: true }がないとHTMLの処理になるので付け忘れないようにしましょう。

app/views/posts/index.html.erb
<%= button_to 'ボタンー', posts_path, method: :get, data: { turbo_stream: true } %>
<div id="hello_rails"></div>

アクションのリスポンスになるindex.turbo_stream.erbを作成します。

app/views/posts/index.turbo_stream.erb
<%= turbo_stream.append "hello_rails", plain: "Hello Rails!" %>

文字を表示されることを確認します。
Image from Gyazo

ログ:

20:48:06 web.1  | Started GET "/posts" for ::1 at 2023-09-09 20:48:06 +0900
20:48:06 web.1  | Processing by PostsController#index as TURBO_STREAM
20:48:06 web.1  |   User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
20:48:06 web.1  |   ↳ app/controllers/application_controller.rb:13:in `current_user'
20:48:06 web.1  |   Post Load (0.2ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC LIMIT ? OFFSET ?  [["LIMIT", 11], ["OFFSET", 0]]
20:48:06 web.1  |   ↳ app/controllers/posts_controller.rb:6:in `index'
20:48:06 web.1  |   User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ?  [["id", 1]]
20:48:06 web.1  |   ↳ app/controllers/posts_controller.rb:6:in `index'
20:48:06 web.1  |   Rendering posts/index.turbo_stream.erb
20:48:06 web.1  |   Rendering text template
20:48:06 web.1  |   Rendered text template (Duration: 0.0ms | Allocations: 4)

ブラウザのレスポンス:

クリックイベントに対して非同期的にページを更新して文字を表示させる例でした。
続いてStreamActionsモジュールを使ってアクションを作成してみます。

StreamActionsモジュールをインポートする

アクションを作成するためにStreamActionsモジュールをインポートします。

app/javascript/console_log.js
import { Turbo } from "@hotwired/turbo-rails"
const { StreamActions } = Turbo

https://github.com/hotwired/turbo-rails/issues/441

console.log()アクションを作成する

アクション名は分かりやすい名前console_logをつけます。
thisキーワードはturbo_streamフレーム中の要素にアクセスすることができます。
要素のmessage属性を取得しconsole.logで表示させてみます。

app/javascript/console_log.js
StreamActions.console_log = function(){
    console.log(this.getAttribute("message"))
}
app/views/posts/index.turbo_stream.erb
<%= turbo_stream_action_tag "console_log", message: "Hello World" %>

turbo_stream_action_tag: これは Turbo Streams でカスタムアクションをトリガーするためのヘルパーメソッドです。第1引数にアクション名(ここでは "console_log")を指定し、第2引数にオプションを持つハッシュを指定します。

"console_log": これはカスタムアクションの名前です。先ほどのコードで定義したカスタム Turbo Stream アクションと対応しています。

message: "Hello World": カスタムアクションのオプションとして、"message" という名前の属性を持つことを指定しています。

application.jsに読み込みます。

app/javascript/application.js
import "./console_log"

実行してみます:
Image from Gyazo

ブラウザのリスポンス:

クリックイベントに対して非同期的にconsole.log()を実行させる例でした。

ブラウザ通知アクション

次ではブラウザの通知APIを使って通知を送ってみます。

https://developer.mozilla.org/en-US/docs/Web/API/notification

app/javascript/notification.js
import { Turbo } from "@hotwired/turbo-rails"
const { StreamActions } = Turbo

StreamActions.notification = function(){
    let title = this.getAttribute("title")
    Notification.requestPermission(function(status){
        console.log(status)
        if (status == "granted"){
            new Notification(title)
        }
    })
}

application.jsに読み込むことを忘れないようにしましょう。

app/views/posts/index.turbo_stream.erb
<%= turbo_stream_action_tag "notification", title: "ブラウザからの通知だ!" %>

通知機能を試すには通知をONにしてから試してください。

ブラウザからのレスポンス:

クリックイベントに対してブラウザから通知を受け取る例でした。
最後はページをリダイレクトさせるアクションを作ります。

ページのリダイレクト

app/javascript/notification.js
import { Turbo } from "@hotwired/turbo-rails"
const { StreamActions } = Turbo

Turbo.StreamActions.redirect = function () {
  const url = this.getAttribute('url') || '/'
  Turbo.visit(url, { frame: '_top', action: 'advance' })
}

こちらもapplication.jsに読み込み込んでいきます。

コントローラー内でリダイレクトURLを指定するのでヘルパーメソッド内にredirectメソッドを作成します。

rails g helper TurboStreams::Redirect
      create  app/helpers/turbo_streams/redirect_helper.rb
app/helpers/turbo_streams/redirect_helper.rb
module TurboStreams::RedirectHelper
  def redirect(url)
    turbo_stream_action_tag("redirect", url: url)
  end
end

Turbo::Streams::TagBuilder.prepend(TurboStreams::RedirectHelper)

Turbo::Streams::TagBuilderクラスは、Turbo StreamsのHTMLタグ(フレームやストリーム要素など)を生成するためのユーティリティクラスです。
このクラスのメソッドを使用することで、Turbo Streamsに含まれる要素を生成し、Turbo Streamsを作成するためのturbo_streamturbo_frameturbo_stream_actionなどのHTML要素を生成されます。

Turbo::Streams::TagBuilderクラスにTurboStreams::RedirectHelperモジュールのメソッドを追加します。

コントローラー内にTurboStreamsモジュールをincludeし、メソッドを使えるようにします。
ルートURLにリダイレクトさせてみます。

app/controllers/posts_controller.rb
include TurboStreams::RedirectHelper
class PostsController < ApplicationController
  def index
    respond_to do |format|
       format.turbo_stream { render turbo_stream: turbo_stream.redirect(root_url) }
       format.html
    end
  end
end

ボタンをクリックしてルートURLにリダイレクトされたことを確認します。
ブラウザからのレスポンス:

オプション

turbo_stream_action_tagのオプションはこちらになります。

turbo_stream_action_tag(action, target: nil, targets: nil, template: nil, **attributes)

"action" (必須): カスタムアクションの名前。これはカスタムJSコードで処理されます。
target: "element_id": アクションが影響を与えるHTML要素のID。アクションが実行された場合、この要素が更新されます。

targets(オプション): Turbo Streamsを適用する複数のHTML要素を指定する場合に使用します。targetsパラメータには配列で要素を指定します。

template(オプション): Turbo Streamsのテンプレート名を指定します。テンプレートを指定すると、そのテンプレートが適用された要素がTurbo Streamsの一部として更新されます。

attributes(その他のオプション): その他のHTML属性を指定できます。これにはdata属性やカスタム属性などが含まれます。

これらのオプションを使って、Turbo Streams をカスタマイズし、クライアント側での動作や表示を制御することができます。
適切なオプションを選択して、Turbo Streams の挙動を調整してください。

https://rubydoc.info/github/hotwired/turbo-rails/Turbo%2FStreams%2FActionHelper:turbo_stream_action_tag

終わりに

turboのカスタムアクションを使ってみました。
デフォルトのアクション以外にいくつでも定義できるところが便利でした。
ヘルパーメソッドと合わせて使うことによって再利用性も高いと感じました。
https://marcoroth.dev/posts/guide-to-custom-turbo-stream-actions

Discussion