This article was originally published onavo.cool
Hotwire is a fantastic technology that helps you build dynamic websites without thinking about JavaScript.
When we re-wrote Avo from VueJS to Hotwire, when it first came out, we had to think about how we could leverage it to our advantage.
One of the first things we did was to add dynamic turbo-frames around common pages.
For example, theResourceIndex
page is the page that usually displays the table with the requested resources (it shows the users on/users
).
We knew that we could use that exact partial when we wanted to display thehas_many
association on theResourceShow
page but didn't know how at first. We could have extracted it to a partial and rendered it in theResourceShow
page underneath the record details, but theResourceShow
page would take a long time to load when you have many associations to one record.
We then came up with the idea to use a turbo-frame for that but still didn't want to use a partial.
Let's say we haveUser
andTeam
models like so:
classTeam<ApplicationRecordhas_many:usersendclassUser<ApplicationRecordbelongs_to:teamend
The routes and controllers look like this:
resources:postsget"/:resource_name/:id/:related_name/",to:"associations#index"
classBaseController<ApplicationControllerdefindex# if there's a query set up, use it, if not, set one upunlessdefined?@query@query=@resource.class.query_scopeendendendclassAssociationsController<BaseControllerdefindex# find the parent record and set the query@query=@parent_model.public_send(params[:related_name])endend
So what happens there? When a user goes to/users
, they will see the list of users, and if they go to/teams/TEAM_ID/users
, they should see the same list of users but scoped to the respective team.
In order to achieve this usingturbo-frame
s we'll add a lazy-loaded turbo frame on theRecordShow
page with thesrc
attribute set to the association path with the?turbo_frame="has_many_users"
param added to the path/teams/TEAM_ID/users?turbo_frame="has_many_users"
url.
<!-- views/base/show.html.erb --><!-- record details here --><!-- association details below --><turbo-frameid="has_many_users"src="/teams/#{@team.id}/users?turbo_frame=has_many_users"target="_top"><!-- Loading state --></turbo-frame>
Next, we should add a dynamic wrap around theindex.html.erb
partial like so.
<!-- views/base/index.html.erb --><%ifparams[:turbo_frame].present?%><turbo-frameid="<%=params[:turbo_frame]%>"><%end%><!-- The list of records --><%ifparams[:turbo_frame].present?%></turbo-frame><%end%>
What happens is that when the user loads a teamsShow
page/teams/1
, that page will display with the lazy-loaded frame (so no impact on the performance of that page on load), which in turn, loads the associationIndex
page with theturbo_frame
param. That will add the<turbo-frame>
tag around the template allowing Turbo to replace the content dynamically on the page.
We're re-using the actualindex.html.erb
template and theBaseController#index
action.
Of course, this can be improved using some helpers and a partial.
# app/helpers/application_helper.rbdefturbo_frame_wrap(name,&block)renderlayout:"partials/turbo_frame_wrap",locals:{name:name}docapture(&block)endend
<!-- app/views/partials/turbo_frame_wrap.html.erb --><%ifname.present?%><turbo-frameid="<%=name%>"><%end%><%=yield%><%ifname.present?%></turbo-frame><%end%>
<!-- views/base/show.html.erb --><!-- record details here --><!-- association details below --><turbo-frameid="<%=turbo_frame%>"src="<%=frame_url%>"target="_top"><!-- Loading state --></turbo-frame>
<!-- views/base/index.html.erb --><%=turbo_frame_wrap(params[:turbo_frame])do%><!-- The list of records --><%end%>
You can do the same thing forShow
pages too. We did with Avo.
You can find a more detailed example on Avo's GitHub repo.
Stay cool and improve performance 💪
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse