Issue with progressively enhancing form submission

I currently have an issue with using turbo frames to progressively enhance a form.

Context
I generally use turbo frames as a way to enhance the user experience by inline presenting forms that have their standalone pages. So my expectation (and why I use Turbo Frames) is that I can build a form —pretending that JS is disabled for the end user— and know that when the user clicks the link “Sign up”, it’ll navigate to the /sign-up page. Only after this is done and working will I then wrap the form in a turbo-frame tag and present it inline on user click in another context.

My specific issue
Inside my form, I have a submit button and a cancel button:

<%= form_with model: contact, url: target_url, local: true do |form| %>
<% errors = []
   errors += contact.errors.full_messages if contact
   errors += association.errors.full_messages if association
%>

<% required_fields ||= [] %>

<% if errors.any? %>
  <div class="bg-red-50 rounded-md p-4 mt-4 border-red-200 border border-1">
    <ul class="list-disc pl-6">
      <% errors.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

  <div class="space-y-12">
    <div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2">
      <div class="">
        <%= form.label :first_name, "First Name", class: required_fields.include?(:first_name) ? "font-bold block text-gray-700 text-sm mb-2" : "block text-gray-700 text-sm mb-2" %>
        <%= form.text_field :first_name, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
      </div>

      <div class="">
        <%= form.label :last_name, "Last Name", class: required_fields.include?(:last_name) ? "font-bold block text-gray-700 text-sm mb-2" : "block text-gray-700 text-sm mb-2" %>
        <%= form.text_field :last_name, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
      </div>

    <div class="flex gap-x-4">
      <%= form.submit button_title, class: "btn-primary" %>
      <%= cancel_button "Cancel", student_path(student) %>
    </div>
  </div>
<% end %>

This form is inside my new.html.erb file:

<div class="main-wrapper">
  <h1 class="page-heading">Create a new user</h1>
  <p>This should not be seen in turbo frame</p>
  <div class="max-w-md">
    <%= turbo_frame_tag dom_id @student, "add-parent" do %>
      <p class="text-sm text-gray-500">Some copy here</p>
      <%= render partial: "contacts/form", locals: {} %>
    <% end %>
  </div>
</div>

When a user clicks “Add parent” button, if JS is enabled, it should present the turbo-frame content inline. That works great. When they click Cancel, it correctly closes out the frame and replaces itself with the original content.

When the user navigates to the full page via URL, everything looks great. When the user clicks Cancel, it correctly navigates back to the correct page.

However, when the user navigates to the full page via URL, and submits the form with errors, the Cancel button does not navigate to the correct page with a full page refresh and instead treats the form as a turbo-frame and tries to replace its contents with a swap from the page that housed the turbo-frame tag. My controller code looks like:

  def create
    @student = Student.find(params[:student_id])

    @contact = Contact.new(allowed_params.merge(organization: @student.organization))
    @association = @student.parent_guardians.build(contact: @contact)

    valid_contact = @contact.valid?
    valid_association = @association.valid?

    respond_to do |format|

      if valid_contact && valid_association
        ActiveRecord::Base.transaction do
          @contact.save!
          @association.save!
        end
        format.html { redirect_to @student, notice: "Successfully created #{@contact.full_name}" }
        format.turbo_stream
      else
        format.html { render :new, status: :unprocessable_entity }
      end

    end
  end

Expected behavior

  • when a user clicks Cancel —and the form was loaded as a turbo-frame— it simply replaces the content with the original content
  • when a user clicks Cancel —and they are on the full page, not loaded via turbo-frame— it correctly navigates to the target URL with a full page refresh

My guess is that I’m doing something wrong that is very simple, but I’m not sure where to look.

1 Like

What your looking for is the target: “_top” option you can set this on the turbo-frame on your new template page. This will make the links inside of your turbo frame navigate the full page instead of only the frame.

<%= turbo_frame_tag dom_id @student, "add-parent", target: "_top" do %>
      <p class="text-sm text-gray-500">Some copy here</p>
      <%= render partial: "contacts/form", locals: {} %>
<% end %>
1 Like

@yunggindigo This is exactly what I needed. Thank you so much. I was previously modifying the target inside the Cancel/Submit links in the frame, which was not working. By modifying the target in the frame, it works as expected.

1 Like