diff --git a/app/channels/turbo/streams/broadcasts.rb b/app/channels/turbo/streams/broadcasts.rb
index de1c0330..8f216fc5 100644
--- a/app/channels/turbo/streams/broadcasts.rb
+++ b/app/channels/turbo/streams/broadcasts.rb
@@ -95,6 +95,14 @@ def refresh_debouncer_for(*streamables, request_id: nil) # :nodoc:
Turbo::ThreadDebouncer.for("turbo-refresh-debouncer-#{stream_name_from(streamables.including(request_id))}")
end
+ def broadcast_morph_to(*streamables, **opts)
+ broadcast_action_to(*streamables, action: :morph, **opts)
+ end
+
+ def broadcast_morph_later_to(*streamables, **opts)
+ broadcast_action_later_to(*streamables, action: :morph, **opts)
+ end
+
private
def render_format(format, **rendering)
ApplicationController.render(formats: [ format ], **rendering)
diff --git a/app/models/concerns/turbo/broadcastable.rb b/app/models/concerns/turbo/broadcastable.rb
index 29970dcd..30e328b3 100644
--- a/app/models/concerns/turbo/broadcastable.rb
+++ b/app/models/concerns/turbo/broadcastable.rb
@@ -491,6 +491,29 @@ def broadcast_render_later_to(*streamables, **rendering)
Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end
+ # Broadcast a morph action to the stream name identified by the passed streamables. Example:
+ # sends My Clearance
+ # to the stream named "identity:2:clearances"
+ # clearance.broadcast_morph_to examiner.identity, :clearances
+ def broadcast_morph_to(*streamables, **rendering)
+ Turbo::StreamsChannel.broadcast_morph_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
+ end
+
+ # Same as broadcast_morph_to but the designated stream is automatically set to the current model.
+ def broadcast_morph(**rendering)
+ broadcast_morph_to(self, target: self, **rendering)
+ end
+
+ # Same as broadcast_morph_to but run asynchronously via a Turbo::Streams::BroadcastJob.
+ def broadcast_morph_later_to(*streamables, **rendering)
+ Turbo::StreamsChannel.broadcast_morph_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
+ end
+
+ # Same as broadcast_morph_later_to but the designated stream is automatically set to the current model.
+ def broadcast_morph_later(target: broadcast_target_default, **rendering)
+ broadcast_morph_later_to self, **rendering
+ end
+
private
def broadcast_target_default
self.class.broadcast_target_default
diff --git a/app/models/turbo/streams/tag_builder.rb b/app/models/turbo/streams/tag_builder.rb
index 99a2aa47..294c4e76 100644
--- a/app/models/turbo/streams/tag_builder.rb
+++ b/app/models/turbo/streams/tag_builder.rb
@@ -228,6 +228,32 @@ def prepend_all(targets, content = nil, **rendering, &block)
action_all :prepend, targets, content, **rendering, &block
end
+ # Morph the target in the dom with either the content passed in or a rendering result determined
+ # by the rendering keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
+ #
+ # <%= turbo_stream.morph "clearance_5", "
Morph the dom target identified by clearance_5
" %>
+ # <%= turbo_stream.morph clearance %>
+ # <%= turbo_stream.morph clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
+ # <%= turbo_stream.morph "clearance_5" do %>
+ # Morph the dom target identified by clearance_5
+ # <% end %>
+ def morph(target, content = nil, **rendering, &block)
+ action :morph, target, content, **rendering, &block
+ end
+
+ # Morph the targets in the dom with either the content passed in or a rendering result determined
+ # by the rendering keyword arguments, the content in the block, or the rendering of the targets as a record. Examples:
+ #
+ # <%= turbo_stream.morph_all ".clearance_item", "Morph the dom target identified by the class clearance_item
" %>
+ # <%= turbo_stream.morph_all clearance %>
+ # <%= turbo_stream.morph_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
+ # <%= turbo_stream.morph_all ".clearance_item" do %>
+ # Morph the dom target identified by the class clearance_item
+ # <% end %>
+ def morph_all(targets, content = nil, **rendering, &block)
+ action_all :morph, targets, content, **rendering, &block
+ end
+
# Send an action of the type name to target. Options described in the concrete methods.
def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block)
template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
diff --git a/test/streams/broadcastable_test.rb b/test/streams/broadcastable_test.rb
index 6460405a..5c086289 100644
--- a/test/streams/broadcastable_test.rb
+++ b/test/streams/broadcastable_test.rb
@@ -224,6 +224,30 @@ class Turbo::BroadcastableTest < ActionCable::Channel::TestCase
@message.broadcast_render_to @profile
end
end
+
+ test "broadcasting morph to stream now" do
+ assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", template: render(@message)) do
+ @message.broadcast_morph_to "stream", target: "message_1"
+ end
+ end
+
+ test "broadcasting morph to stream now targeting children-only children-only" do
+ assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", 'children-only': true, template: render(@message)) do
+ @message.broadcast_morph_to "stream", target: "message_1", attributes: { 'children-only': true }
+ end
+ end
+
+ test "broadcasting morph now" do
+ assert_broadcast_on @message.to_gid_param, turbo_stream_action_tag("morph", target: "message_1", template: render(@message)) do
+ @message.broadcast_morph target: "message_1"
+ end
+ end
+
+ test "broadcasting morph now targeting children-only" do
+ assert_broadcast_on @message.to_gid_param, turbo_stream_action_tag("morph", target: "message_1", 'children-only': true, template: render(@message)) do
+ @message.broadcast_morph target: "message_1", attributes: { 'children-only': true }
+ end
+ end
end
class Turbo::BroadcastableArticleTest < ActionCable::Channel::TestCase
@@ -518,6 +542,30 @@ class Turbo::SuppressingBroadcastsTest < ActionCable::Channel::TestCase
end
end
+ test "suppressing broadcasting morph to stream now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_morph_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting morph to stream later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_morph_later_to "stream"
+ end
+ end
+
+ test "suppressing broadcasting morph now" do
+ assert_no_broadcasts_when_suppressing do
+ @message.broadcast_morph
+ end
+ end
+
+ test "suppressing broadcasting morph later" do
+ assert_no_broadcasts_later_when_supressing do
+ @message.broadcast_morph_later
+ end
+ end
+
private
def assert_no_broadcasts_when_suppressing
assert_no_broadcasts @message.to_gid_param do
@@ -535,5 +583,3 @@ def assert_no_broadcasts_later_when_supressing
end
end
end
-
-
diff --git a/test/streams/streams_channel_test.rb b/test/streams/streams_channel_test.rb
index 5b015edb..433092a1 100644
--- a/test/streams/streams_channel_test.rb
+++ b/test/streams/streams_channel_test.rb
@@ -255,4 +255,34 @@ class Turbo::StreamsChannelTest < ActionCable::Channel::TestCase
Turbo::StreamsChannel.broadcast_stream_to "stream", content: "direct"
end
end
+
+ test "broadcasting morph now" do
+ options = { partial: "messages/message", locals: { message: "hello!" } }
+
+ assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", template: render(options)) do
+ Turbo::StreamsChannel.broadcast_morph_to "stream", target: "message_1", **options
+ end
+
+ assert_broadcast_on "stream", turbo_stream_action_tag("morph", targets: ".message", template: render(options)) do
+ Turbo::StreamsChannel.broadcast_morph_to "stream", targets: ".message", **options
+ end
+ end
+
+ test "broadcasting morph later" do
+ options = { partial: "messages/message", locals: { message: "hello!" } }
+
+ assert_broadcast_on "stream", turbo_stream_action_tag("morph", target: "message_1", template: render(options)) do
+ perform_enqueued_jobs do
+ Turbo::StreamsChannel.broadcast_morph_later_to \
+ "stream", target: "message_1", **options
+ end
+ end
+
+ assert_broadcast_on "stream", turbo_stream_action_tag("morph", targets: ".message", template: render(options)) do
+ perform_enqueued_jobs do
+ Turbo::StreamsChannel.broadcast_morph_later_to \
+ "stream", targets: ".message", **options
+ end
+ end
+ end
end