From f7b07737d1700d4795e8f981ffb3d67a1d3b3a54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Radan=20Skori=C4=87?= <radan.skoric@gmail.com>
Date: Fri, 24 May 2024 16:07:30 +0200
Subject: [PATCH] Explain how stream actions can be used within HTML

As a consequence of the fundamental way in which
Stream actions are implemented, they can also be
executed by rendering them within any HTML that's
included on the dom.

This can be useful in a variety of situations.

Some examples:

You might have implemented a very elegant flow using pure Turbo Frames but there is just some small extra thing that needs to happen when the Turbo Frame loads. For example: update a counter or modify a small related piece that's outside the frame.

Depending on the details of your case you could use full page Turbo Morphing. If that won't work, you could refactor everything to just return a turbo streams response. The turbo frame response could become a turbo stream `replace` action and then you could add more actions.

But sometimes you want to make the minimal change needed to make it work. For that you could render the stream action for the side-effects *inside the frame* and rely on the feature we are discussing here. You achieve the side-effect with minimal changes and keep the main logic simple.

For GET requests Turbo will **not** expect a Turbo streams response and if you do return a Turbo stream response (i.e. Content-type of `text/vnd.turbo-stream.html` instead of `text/html`), it will not attempt to process it as such. It will simply not work. The assumption is that you're either updating a full page or one frame.

This means that you can't use Turbo streams on get requests to update multiple parts of the page. However, you can insert streams into the primary HTML response to achieve the same.

Be very careful with this and think twice before using it. In most cases you probably don't need it but Ruby and Rails are all about sharp tools given to you to use wisely. This is another one.

In a lot of legacy applications you'll find inline `<script>` tags with the HTML code to be executed when HTML is loaded.

Stream actions inside HTML can eliminate any need for inline javascript. Anything you would want to do with a custom piece of javascript can be done more elegantly and cleanly with a Turbo Stream Action rendered inside the HTML.

For example, triggering frontend analytics tracking from server side or opening a UI widget (like a modal) on page load. This is not uncommon in legacy applications as sometimes the easiest way to get it working is to just use inline javascript. Instead, you could create a custom Stream Action, implement the logic on it, and then render just the stream action tag. The code will be cleaner and more maintainable.

This will also allow you to easily get to the point where you can configure the [script-src Content-Security-Policy rule to disallow inline scripts](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_inline_script), which is one of the biggest security wins CSP provides.
---
 _source/handbook/05_streams.md |  6 ++++--
 _source/reference/streams.md   | 11 +++++++++++
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/_source/handbook/05_streams.md b/_source/handbook/05_streams.md
index 58807a3..9a092ed 100644
--- a/_source/handbook/05_streams.md
+++ b/_source/handbook/05_streams.md
@@ -75,7 +75,7 @@ A Turbo Streams message is a fragment of HTML consisting of `<turbo-stream>` ele
 
 <turbo-stream action="morph" target="current_step">
   <template>
-    <!-- The contents of this template will replace the 
+    <!-- The contents of this template will replace the
     element with ID "current_step" via morph. -->
     <li>New item</li>
   </template>
@@ -83,7 +83,7 @@ A Turbo Streams message is a fragment of HTML consisting of `<turbo-stream>` ele
 
 <turbo-stream action="morph" target="current_step" children-only>
   <template>
-    <!-- The contents of this template will replace the 
+    <!-- The contents of this template will replace the
     children of the element with ID "current_step" via morph. -->
     <li>New item</li>
   </template>
@@ -100,6 +100,8 @@ resolved by an [id](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att
 
 You can render any number of stream elements in a single stream message from a WebSocket, SSE or in response to a form submission.
 
+Also, any stream element that's connected to the page dom will be interpreted, i.e. the stream action will execute and the element be removed from the dom. For example, this can be used to execute stream actions when a page or a frame is loaded.
+
 ## Actions With Multiple Targets
 
 Actions can be applied against multiple targets using the `targets` attribute with a CSS query selector, instead of the regular `target` attribute that uses a dom ID reference. Examples:
diff --git a/_source/reference/streams.md b/_source/reference/streams.md
index b065a2b..90e5ccd 100644
--- a/_source/reference/streams.md
+++ b/_source/reference/streams.md
@@ -146,3 +146,14 @@ To target multiple elements with a single action, use the `targets` attribute wi
 Turbo can connect to any form of stream to receive and process stream actions. A stream source must dispatch [MessageEvent](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) messages that contain the stream action HTML in the `data` attribute of that event. It's then connected by `Turbo.session.connectStreamSource(source)` and disconnected via `Turbo.session.disconnectStreamSource(source)`. If you need to process stream actions from different source than something producing `MessageEvent`s, you can use `Turbo.renderStreamMessage(streamActionHTML)` to do so.
 
 A good way to wrap all this together is by using a custom element, like turbo-rails does with [TurboCableStreamSourceElement](https://github.com/hotwired/turbo-rails/blob/main/app/javascript/turbo/cable_stream_source_element.js).
+
+## Stream Elements inside HTML
+
+Turbo streams are implemented as [a custom HTML element](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements).
+The element is interpreted as part of the `connectedCallback` function that the browser calls when the element is
+connected to the page dom.
+
+This means that any stream elements that are rendered into the dom will be interpreted. After being interpreted, Turbo
+will remove the element from the dom. More specifically, it means that rendering stream actions inside the page or
+frame content HTML will cause them to be executed. This can be used to execute additional sideffects beside the main content
+loading.