Let's try to edit a task with the DevTools' Network tab open. Notice that the server builds and sends the whole response HTML, which is about 5.2 kB. Upon receiving the response HTML, Turbo Drive extracts and replaces the body.
Let's improve this functionality by rendering the edit form in-place on the index page, instead of taking the user to a separate page. We will achieve this in three simple steps using Turbo Frames.
Highlight the Turbo Frames
Well, this is an optional step, but it will make understanding Turbo Frames easy. Turbo Frames are just custom HTML elements and are invisible by default. During development, it's useful to highlight them to understand what's going on.
Let's add some CSS to make them visible. Add this code in the app/assets/stylesheets/application.css
file.
turbo-frame {
border: 1px solid lightblue;
border-radius: 5px;
padding: 0.1em 1em;
margin: 1em 0;
}
Wrap the Task in a Turbo Frame
Frames are created by wrapping a segment of the page in a <turbo-frame>
element. Since we want to update the task we want to edit with the form to insert the new task, let's wrap the task in a Turbo Frame element.
<turbo-frame id="<%= dom_id task %>" class="block">
...
<%= link_to "Edit", edit_task_path(task), class: "btn bg-gray-100" %>
...
</turbo-frame>
Notice that we're also using the Rails' dom_id
helper to generate a unique ID for each <turbo-frame>
element. Each frame element must have a unique ID, which is used to match the content being replaced when requesting new pages from the server.
Also, note that we added class block
to the frame. Since <turbo-frame>
elements are custom elements, and all custom elements are displayed inline
by default, we need to make sure they're displayed as block
elements.
After reloading the browser, this is how it should look.
Wrap the Response in a Turbo Frame
This is how Turbo Frames work: any links and forms inside a frame are captured, and the frame contents are automatically updated after receiving a response.
When the link to edit the task is clicked, the response provided by /tasks/1/edit
has its <turbo-frame id="task_1">
segment extracted, and the content replaces the frame from where the click originated.
Update the edit.html.erb
response by wrapping the form inside a <turbo-frame>
tag.
<div>
<h1 class="font-bold text-2xl mb-3">Editing Task</h1>
<turbo-frame id="<%= dom_id @task %>">
<%= render "form", task: @task %>
<%= link_to "Never Mind", tasks_path, class: "btn mb-3 bg-gray-100" %>
</turbo-frame>
</div>
Notice that we haven't wrapped the <h1>
element inside the <turbo-frame>
tag. This is because whether the server provides a full document, or just a fragment containing an updated version of the requested frame, only that particular frame will be extracted from the response to replace the existing content.
That's it. We are done. Reload the page and be prepared to be amazed.
Upon clicking the Edit button, the edit form is rendered inline, without having to redirect to a separate page.
Notice that the <h1>
tag containing the text Editing Task is not showing up. This is because Turbo replaces the existing <turbo-frame>
tag with the matching <turbo-frame>
tag with the same ID. Everything else is ignored.
What's more, if you open the Network tab and click Edit, you will see the response is only 2.1 kB, and only contains the template, without any layout. This is because Rails being smart, sets the layout to false
if it's a <turbo-frame>
request. For more details, checkout the frame_request.rb
file in the turbo-rails
repository.
Upon editing and hitting Save or Never Mind buttons, Turbo conveniently gets the response from the server and replaces the edit form with the now updated task.
Now comment out the CSS that makes the <turbo-frame>
tags visible, and our application feels even more impressive.
Note: You'll have to update the delete
button as follows for it to work.
<%= button_to "Delete", task_path(task), method: :delete, data: { "turbo-frame": "_top" }, class: "btn bg-red-100" %>