turbo-frames-tabs

In the previous post we did tabbed content with turbo streams by replacing a DOM ID with a template served by a POST request.

Now we will add tabbed content functionality with turbo frames.

Use the boilerplate functionality from the previous post.

1. Turbo Frames. Separate controller actions for each dropdown. #

  • for turbo frames, you can use GET routes without any problems.

app/config/routes.rb

  resources :projects do
    member do
      get :comments
      get :tasks
    end
  end
  • respond with html
  • you can render either partial or template. I just prefer partial here. Does not matter much.
  • turbo_frame_request? - to make this format.html available ONLY via turbo request, not as a separate page
  • else redirect - if someone tries to open a tab in a new tab

turbo_frame_request source code

app/controllers/projects_controller.rb

  def comments
    if turbo_frame_request?
      respond_to do |format|
        format.html { render partial: 'projects/comments',
          locals: { comments: @project.comments, project: @project }}
      end
    else
      redirect_to @project, alert: "Not allowed"
    end
  end

  def tasks
    if turbo_frame_request?
      respond_to do |format|
        format.html { render partial: 'projects/tasks',
          locals: { tasks: @project.tasks, project: @project }}
      end
    else
      redirect_to @project, alert: "Not allowed"
    end
  end
  • create a partial with links
  • request.url - just for you to see the current url that is being rendered inside the frame ;)
  • style - to highlight the current link

app/views/projects/_tabs.html.erb

<%= request.url %>
<br>
<%= link_to "Tasks", tasks_project_path(@project),
      style: "#{"font-weight: bold" if current_page?(tasks_project_path(@project))}" %>
<%= link_to "Comments", comments_project_path(@project),
      style: "#{"font-weight: bold" if current_page?(comments_project_path(@project))}" %>
  • create a turbo frame. give it a name
  • render the partial with tabs inside

app/views/projects/show.html.erb

<%= turbo_frame_tag 'frame_dropdowns' do %>
  <%= render partial: "projects/tabs" %>
<% end %>
  • wrap content of tasks and comments into a turbo_frame_tag
  • don’t forget to render the _tabs partial

app/views/projects/_tasks.html.erb

<%= turbo_frame_tag 'frame_dropdowns' do %>
  <%= render partial: 'projects/tabs' %>
  <h3>
  Tasks for project #
  <%= project.id %>
  </h3>
  <ul>
  <% tasks.each do |task| %>
    <li>
      <%= task.id %>
      <%= task.name %>
    </li>
  <% end %>
  </ul>
<% end %>

app/views/projects/_comments.html.erb

<%= turbo_frame_tag 'frame_dropdowns' do %>
  <%= render partial: 'projects/tabs' %>
  <h3>
  Comments for project #
  <%= project.id %>
  </h3>
  <ul>
  <% comments.each do |comment| %>
    <li>
      <%= comment.id %>
      <%= comment.name %>
    </li>
  <% end %>
  </ul>
<% end %>

2. Default open tab #

app/views/projects/show.html.erb

<%= turbo_frame_tag 'frame_dropdowns' do %>
  <%= render partial: "projects/tasks", locals: { project: @project, tasks: @project.tasks } %>
<% end %>

3. Using template variants to respond only with a turbo frame #

class ProjectsController < ApplicationController
  before_action :set_project
  before_action :turbo_frame_request_variant

  def show
  end

  def comments
    respond_to do |format|
      format.html { render template: 'projects/comments', 
                           locals: { comments: @project.comments, project: @project }}
    end
  end

  def tasks
    respond_to do |format|
      format.html { render template: 'projects/tasks', 
                           locals: { tasks: @project.tasks, project: @project }}
    end
  end

  private

  def turbo_frame_request_variant
    request.variant = :turbo_frame if turbo_frame_request?
  end

  def set_project
    @project = Project.find(params[:id])
  end
end
  • rename partials to templates, add +turbo_frame estention to the templates to respond with them
    -- _comments.html.erb
    -- _tasks.html.erb
    ++ comments.html+turbo_frame.erb
    ++ tasks.html+turbo_frame.erb
    
  • Now, when someoone tries to open a new tab, they will get a Template is missing error.

Conclusion: #

  • Rendering tabbed content with turbo frames feels more natural (than with STREAMS)
  • Frames use GET request
  • Streams use POST, PATCH, UPDATE, DELETE requests
  • Streams and Frames can exist independently from each other