Hotwire Demo — Turbo Frame

Decomposes Pages Into Independent Contexts HotwireSeries_Episode#1

J3
Jungletronics
6 min readAug 24, 2023

--

This post is a continuation of this intro.

In this post :

. Learn about Turbo Frame.

We are now prepared to delve into Hotwire!

For your convenience, we have uploaded this code to my GitHub repository. Feel free to start without needing to type anything on your keyboard.

👉️ GitHub repo

First:

TURBO FRAME!

Turbo Frames decomposes pages into independent contexts, which can be lazy-loaded and scoped for interaction.

Turbo frames can be lazy-loaded and scoped for interaction.

This sentence is referring to two concepts related to Turbo Frames in the context of Hotwire:

  1. Lazy Loading: “Turbo frames can be lazy loaded.” This means that Turbo Frames can be loaded into the page only when they become visible in the user’s viewport. In other words, the content within the Turbo Frame won’t be loaded until it’s needed or until the user scrolls to that part of the page. This can help improve the initial loading performance of a web page by deferring the loading of non-essential content until necessary.
  2. Scoped Interaction: “Scoped for interaction” suggests that the Turbo Frames provide an isolated scope for user interaction. When you interact with elements or perform actions within a Turbo Frame, those interactions are contained within that specific frame. This isolation can help manage the state and behavior of different sections of the page separately, making the application more modular and easier to manage.

In essence, Turbo Frames in Hotwire provide the capability to load content on-demand (lazy loading) while keeping interactions within a specific scope. This combination of features contributes to a more efficient and interactive user experience on web pages.

0#Step — Let’s apply a noticeable blue border to turbo-frame in order to readily identify the frame that will undergo dynamic changes.

GoTo:

chat/app/assets/stylesheets/application.css

turbo-frame {
display: block;
border: 1px solid blue;
}

In the absence of a Turbo Frame, when we click Edit, we are directed to a new page containing the form, which takes us away from the main page for editing the room title. By utilizing Turbo Frame, we consolidate all these actions within a single page, achieving a single-page application (SPA) experience.

Click Edit…
…and a new page http://127.0.0.1:3000/rooms/1/edit will be rendered. Let’s use SPA!

1#Step — Let us employ Turbo Frames to enable dynamic updates within the room frame, which contains the editing form.

Go To:

chat/app/views/rooms/show.html.erb

<p id="notice" style="color: green"><%= notice %></p>
<turbo-frame id="room">
<%= render @room %>
<p>
<%= link_to "Edit", edit_room_path(@room) %>
<%= link_to "Back", rooms_path %>
</p>
</turbo-frame>
<div id="messages">
<%= render @room.messages %>
</div>
<%= link_to "New Message", new_room_message_path(@room) %>

The central element of this code snippet is the following segment:

<turbo-frame id="room">
<!-- ... -->
</turbo-frame>

This section uses the Turbo Frames feature, which is part of the Hotwire framework. It wraps the content inside a frame with the id attribute set to room. The content inside will be replaced dynamically using Turbo Streams when changes occur.

This specific section leverages the Turbo Frames feature, an integral aspect of the Hotwire framework. It encapsulates the enclosed content within a frame, distinctly identified by the room id attribute. As changes transpire, Turbo Streams will seamlessly and dynamically replace the content within this frame.

2#Step — Go To:

chat/app/views/rooms/edit.html.erb

<h1>Editing room</h1>
<turbo-frame id="room">
<%= render "form", room: @room %>
<br>
</turbo-frame>
<div>
<%= link_to "Show this room", @room %> |
<%= link_to "Back to rooms", rooms_path %>
</div>

This is the target page. Let’s discuss the rules applied to Turbo Frames:

Rule #1: When clicking a link within a Turbo Frame, 
Turbo expects a frame with
the identical ID on the target page.

The central element of this code snippet is <turbo-frame id="room"> ... </turbo-frame>: This part employs the Turbo Frames feature. It defines a frame with the ID attribute set to room. The frame contains the rendered content from the _form partial, with the room variable being passed as a parameter. This setup will update specific parts of the page dynamically without reloading the entire page (see show.html.erb).

Now, upon clicking “Edit”…
…the page remains unchanged, while all the editing interactions are confined within the Turbo Frame. This exemplifies the fundamental essence of the Single Page Application (SPA) technique.
Witness the ultimate outcome: everything encapsulated within a single page! Truly fantastic!

For completeness:

Rule #2: If there is no Turbo Frame with 
the same ID on the target page,
the frame desappears, raising an
ERROR response (no matching element)
on the console.

And finally:

Rule #3: A link can target another frame 
than the one it is directly nested in
- use data-turbo-frame attribute

More info about adding the data-turbo-frame attribute on non-frame elements to control this, click here.

3#Step — If you conduct a test, you’ll notice that the Back link is currently malfunctioning. To address this, follow these steps:

Complete the following line:

chat/app/views/rooms/show.html.erb

<%= link_to "Back", rooms_path, "data-turbo-frame": "_top" %>

By incorporating the data-turbo-frame attribute as shown above. This attribute is set to _top, enabling a breakout from the frame in a manner reminiscent of traditional HTML frames.

As a result of this adjustment, the Back link will now function as intended, allowing seamless navigation, while the frame continues to encapsulate the Edit Display Loop.

4#Step —Let’s incorporate the New Message link within an inline Turbo Frame tag that loads lazily. This frame will be fetched immediately after the initial page load:

Navigate to: chat/app/views/rooms/show.html.erb

Replace the line:

<%= link_to "New Message", new_room_message_path(@room) %>

By this one:

<turbo-frame id="new_message" src="<%= @room.id %>/messages/new" target="_top">
</turbo-frame>

5#Step — Now the target page:

chat/app/views/messages/new.html.erb

<h1>New Message</h1>
<turbo-frame id="new_message" target="_top">
<%= form_with(model: [@message.room, @message],
data:{ controller:"reset_form", action: "turbo:submit-end->reset_form"}) do |form| %>
<div class="field">
<%= form.text_field :content %>
<%= form.submit "Send"%>
</div>
<% end %>
</turbo-frame>
<%= link_to 'Back', @message.room %>
Now, when you click “Send”…
…the inline frame ensures that our task remains prominently visible on the same page. This is truly awesome again!

Now Let’s turn to TURBO STREAMS!

However, this topic will be addressed in the upcoming post!

See you soon!

👉️GitHub — Hotwire_v2

Related Posts:

0#Episode — HotwireSeries — Intro — Hotwire Demo — What is the purpose of Hotwire in Rails 7?

1#Episode — HotwireSeries — Turbo Frame — Decomposes Pages Into Independent Contexts (this one)

2#Episode — HotwireSeries — Turbo Stream — Pushing HTML Updates To The Client Using WebSockets

3#Episode — HotwireSeries — Stimulus — A lightweight JavaScript Framework

GitHub cmds:

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
modified: app/assets/stylesheets/application.css
modified: app/views/messages/new.html.erb
modified: app/views/rooms/edit.html.erb
modified: app/views/rooms/show.html.erb

no changes added to commit (use "git add" and/or "git commit -a")

$ git branch --list

$ git add -A

$ git commit -m ":sparkles: feat: Add Turbo Frame"
[master 2214db2] :sparkles: feat: Add Turbo Frame
5 files changed, 57 insertions(+), 31 deletions(-)

$ git push
Enumerating objects: 25, done.
Counting objects: 100% (25/25), done.
Delta compression using up to 12 threads
Compressing objects: 100% (12/12), done.
Writing objects: 100% (13/13), 1.79 KiB | 1.79 MiB/s, done.
Total 13 (delta 8), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (8/8), completed with 8 local objects.
To github.com:giljr/hotwire_rails_tutorial.git
11d4a66..2214db2 master -> master

$ git tag -a Hotwire_v2 -m "Turbo Tech - How it Works! Go to https://hotwired.dev/" -m "0- Work with Turbo Frame." -m "Thank you for downloading this project 😘️👌️👋️😍️"

$ git push origin Hotwire_v2
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 274 bytes | 274.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:giljr/hotwire_rails_tutorial.git
* [new tag] Hotwire_v2 -> Hotwire_v2

Fix_1: Step#4 — thanks to Sia Davarnia on Jan 2024

--

--

J3
Jungletronics

😎 Gilberto Oliveira Jr | 🖥️ Computer Engineer | 🐍 Python | 🧩 C | 💎 Rails | 🤖 AI & IoT | ✍️