Custom Turbo Stream Actions

Episode #382 by Teacher's Avatar David Kimura

Summary

With the release of Turbo 7.2, we gained the ability to create custom actions in Turbo. This allows us to trigger functions on the client side that would have been difficult or cumbersome to do in the past. In this episode, we look at setting up custom actions and how to use them.
rails turbo javascript hotwire 15:20

Chapters

  • Introduction (0:00)
  • Tutorial Begins (2:51)
  • Organizing the Javascript (3:17)
  • Creating Toast Custom Turbo Stream (4:10)
  • Demo of the Toast Custom Action (6:19)
  • Setting up Custom Turbo Stream Actions on the Ruby side (7:27)
  • Using the custom action in a Rails controller (9:08)
  • Demo of Rails rendering the custom actions (9:43)
  • Triggering multiple actions (10:00)
  • Console.log custom action (10:29)
  • Broadcasting custom actions (11:49)
  • Demo broadcasted custom actions (13:40)
  • Searching trick on Drifting Ruby (13:57)
  • Turbo Power (14:25)
  • Final Thoughts (14:40)

Resources

Download Source Code

Summary

# Terminal
rails g helper TurboStreams::Toast
rails g helper TurboStreams::Log
yarn add toastify-js

# views/welcome/index.html.erb
<turbo-stream action="toast" message="Hello World"></turbo-stream>

# app/javascript/application.js
import "./turbo_streams"

# app/javascript/turbo_streams/index.js
import "./toast"
import "./log"

# app/assets/stylesheets/application.bootstrap.scss
@use "toastify-js/src/toastify.css";

# app/javascript/turbo_streams/toast.js
import { StreamActions } from "@hotwired/turbo"
import Toastify from "toastify-js"

StreamActions.toast = function() {
  const message = this.getAttribute("message")
  const position = this.getAttribute("position")
  Toastify({
    text: message,
    duration: 3000,
    destination: "",
    close: true,
    gravity: "top",
    position: position,
    stopOnFocus: true,
    style: {
      background: "linear-gradient(to right, #00b09b, #96c93d)",
    }
  }).showToast()
}

# app/javascript/turbo_streams/log.js
import { StreamActions } from "@hotwired/turbo"

StreamActions.log = function() {
  const message = this.getAttribute("message")
  console.log(message)
}

# app/helpers/turbo_streams/toast_helper.rb
module TurboStreams::ToastHelper
  def toast(message, position: "left")
    turbo_stream_action_tag :toast, message: message, position: position
  end
end
Turbo::Streams::TagBuilder.prepend(TurboStreams::ToastHelper)

# app/helpers/turbo_streams/log_helper.rb
module TurboStreams::LogHelper
  def log(message)
    turbo_stream_action_tag :log, message: message
  end
end
Turbo::Streams::TagBuilder.prepend(TurboStreams::LogHelper)

# welcome_controller.rb
class WelcomeController < ApplicationController
  def index
  end

  def page1
    render turbo_stream: turbo_stream.toast("hello from a rails controller", position: :right)
  end

  def page2
    render turbo_stream: [
      turbo_stream.log("log from page 2"),
      turbo_stream.toast("hello from page 2", position: :left),
      turbo_stream.toast("hello from page 2 and this is a second toast", position: :right),
    ]
  end
end

# models/user.rb
class User < ApplicationRecord
  include Turbo::Streams::ActionHelper
  include Turbo::Streams::StreamName

  after_create -> {
    content = turbo_stream_action_tag(:toast, message: "A new user was created")
    ActionCable.server.broadcast(stream_name_from(:users), content)
  }
end

# views/users/index.html.erb
<%= turbo_stream_from :users %>