In this tutorial, we will explore how to use Turbo Frames for creating dynamic form fields in a Rails application. We will create a form that allows users to sign up as an individual or a company. Depending on their choice, the form fields will be updated accordingly.

Scenario

Our form will initially have the following fields for signing up as an individual:

  1. Name
  2. Date of Birth
  3. Email
  4. Password

When the user chooses to sign up as a company, the form should remove the 'Date of Birth' field and replace 'Name' with 'Company Name' and add a 'Tax Number' field.

Setup

First, we start by creating an Account model with an enum for account_type. Enums in Rails allow you to define a list of possible values for a given attribute. In our case, the account type can be either 'individual' or 'company'. Enums also provide helper methods like individual? and company? to check the account type.

# app/models/account.rb
class Account < ApplicationRecord
  enum account_type: {
    individual: "individual",
    company: "company"
  }
end

We've already created the form with the necessary fields, and we will now add radio buttons to toggle between signing up as an 'Individual' or a 'Company'.

<!-- app/views/registrations/new.html.erb -->
<%= f.label :account_type, value: "individual", class: "flex items-center" do %>
  <%= f.radio_button :account_type, "individual", class: radio_button_class %>
  <span class="ml-1.5 sm:ml-2.5"><span class="hidden sm:inline">an </span>individual</span>
<% end %>

<%= f.label :account_type, value: "company", class: "flex items-center" do %>
  <%= f.radio_button :account_type, "company", class: radio_button_class %>
  <span class="ml-1.5 sm:ml-2.5"><span class="hidden sm:inline">a </span>company</span>
<% end %>

Our form will now show the additional fields for company sign-up, but they will not be hidden or shown dynamically based on the radio button selected.

Adding TurboFrame tags

We will wrap the top fields in a TurboFrame tag to make the form fields dynamic.

<!-- app/views/registrations/new.html.erb -->
<%= turbo_frame_tag "account_types" do %>
  <!-- All the different Fields to toggle -->
  <% if @account.company? %>
    <!-- Company fields -->
  <% else %>
    <!-- Individual fields -->
  <% end %>
<% end %>

By wrapping the fields inside a TurboFrame, we can load and update them independently from the rest of the page. To do this, we will use a StimulusJS NavigateController to listen for radio button changes and trigger the TurboFrame navigation.

First, create a new StimulusJS controller called navigate_controller.js

// app/javascript/controllers/navigate_controller.js
import { Controller } from "@hotwired/stimulus";

/*
 * Usage
 * =====
 *
 * add data-controller="navigate" to the turbo frame you want to navigate
 *
 * Action (add to radio input):
 * data-action="change->navigate#to"
 * data-url="/new?input=yes"
 *
 */
export default class extends Controller {
  to(e) {
    e.preventDefault();

    const { url } = e.target.dataset;

    this.element.src = url;
  }
}

Next, attach the Navigate controller to the TurboFrame tag.

<!-- app/views/registrations/new.html.erb -->
<%= turbo_frame_tag "account_types", data: { controller: "navigate" } do %>
  <!-- All the different Fields to toggle -->
<% end %>

Finally, we add a data-action attribute to each radio button and set the proper URL for individual and company account creation:

<!-- app/views/registrations/new.html.erb -->
<%= f.label :account_type, value: "individual", class: "flex items-center" do %>
  <%= f.radio_button :account_type, "individual", class: radio_button_class, data: { action: "change->navigate#to", url: new_registration_path({ account: { account_type: "individual" } }) } %>
  <span class="ml-1.5 sm:ml-2.5"><span class="hidden sm:inline">an </span>individual</span>
<% end %>

<%= f.label :account_type, value: "company", class: "flex items-center" do %>
  <%= f.radio_button :account_type, "company", class: radio_button_class, data: { action: "change->navigate#to", url: new_registration_path({ account: { account_type: "company" } }) } %>
  <span class="ml-1.5 sm:ml-2.5"><span class="hidden sm:inline">a </span>company</span>
<% end %>

Now, clicking the radio buttons will trigger the navigate method of our controller and load the appropriate form fields inside the TurboFrame.

Updating the Controller

Our Rails controller needs to handle the dynamic URL parameters for the different account types.

# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
  def new
    # We construct the new instance here using the account params
    # which allows our URLs to pass in query params to the new 
    # form, switching the fields appropriately.
    @account = Account.new(account_params)
  end

  def create
    @account = Account.new(account_params)

    if @account.save
      redirect_to signup_success_path
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def account_params
    params.require(:account).permit(
      :account_type, :name, :company_number, :date_of_birth, :email, :password
    )
  rescue
    {}
  end
end

This will ensure that the correct account type is set when navigating between the individual and company radio buttons.

Conclusion

This tutorial demonstrated using Turbo Frames and a sprinkling of stimulus to create dynamic form fields in a Rails application. By leveraging Turbo Frames, we could easily update and load the form fields independently from the rest of the page based on the user's selected account type.

Feel free to leave any questions or comments below, and share any alternative methods you've used for accomplishing dynamic form fields in Rails.

Discussion (0)

To comment you need to sign up for a free account or sign in.

Get new videos in your inbox weekly!

Be the first to hear about new courses and videos launching. You’ll only receive relevant news, no spam.