Flash messages with Turbo Streams (Bootstrap Toast)
-
Source Repository: https://github.com/swobspace/rails-playground
-
Bootstrap v5 Toast: https://getbootstrap.com/docs/5.1/components/toasts
-
Rails Playground: used in lists on update tasks, not on create/destroy. For create/destroy we use Bootstrap Alert (see Flash messages with Turbo Streams (Bootstrap Alert))
The goal: show flash messages from turbo_stream via Bootstrap Toast
Rails controller: set the flash message
def create
@task = @list.tasks.build(task_params)
respond_with(@task, location: location) do |format|
if @task.save
format.turbo_stream { flash.now[:notice] = "Task successfully created" }
end
end
end
We use flash.now here since there is no redirect with turbo_stream.
|
respond_with since we uses the responders gem from https://github.com/heartcombo/responders. Your mileage may vary. Plain rails 7 generators don’t use respond_to (nor respond_with ). See app/controllers/categories_controller.rb .
|
Layout
Very simple: just a <div>
with an id
where to show flash messages from turbo_stream rendering. Otherwise render existing flashes as usual. In case of turbo_stream rendering the content of the <div>
will be replaced by turbo_stream content.
<div class="container-fluid">
<div id="flash"> (1)
<%= render "shared/flash_toast" %>
</div>
<%= yield %>
</div>
1 | id for turbo_stream replacing |
A Helper to render Turbo Toasts
A helper to call from each create|update|destroy.turbo_stream.erb
view: simply add the following snippet to each view:
<%= render_turbo_toast %>
A view component for toasts
Since flashes goes with keys like :alert, :error, :notice, we need those translated in Bootstrap jargon like 'danger', 'info' or 'success'.
# frozen_string_literal: true
class ToastComponent < ViewComponent::Base
def initialize(severity:, message:)
@level = update_severity(severity)
@message = message
end
private
# bootstrapify names
def update_severity(severity)
case severity.to_sym
when :alert, :error
"danger"
when :notice
"info"
else
severity.to_s
end
end
end
<div class="position-fixed top-0 end-0 p-3" style="z-index: 2000">
<div id="liveToast"
class="toast align-items-center border-0 text-white bg-<%= @level %>"
role="alert" aria-live="assertive" aria-atomic="true"
data-controller="toast">
<div class="d-flex">
<div class="toast-body">
<%= @message %>
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto"
data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
z-index: 2000 to overcome the z-index coming with our navbar (z-index: 1030 )
|