mateo
mateo

Reputation: 319

Rails 7 + hotwire. Is it possible to turbo_replace form part not whole form?

I have no idea if is it possible to do.

I have form for company Profile. Some data (17 fields in company_data partial) can be fetch using tax_number. Is is possible to replace turbo_frame 'profile_company_data' with controler turbo_stream.replace?

How to get f (form_builder object) in turbo_stream - it is needed to render partial in turbo response.

= form_with model: @profile, url: profile_path  do |f|
  .columns 
    .column.is-4
      .field
        =f.label :tax_number, 'Tax number', class: 'label is-required'
        .control 
          = f.text_field :tax_number, class: 'input', maxlength: "13"
    .column.is-2 
      .button.is-primary{onClick -> 'fetch_company_data()'} Read company Data
  = turbo_frame_tag 'profile_company_data' do
    = render 'company_data', f: f

  .block
    = f.fields_for :main_address do |addr|
      = render 'address_fields', f: addr

I've tried render this snippet inside turbo response. It works but it creates nested form tag with new csrf-token.

= form_with model: @profile, url: profile_path  do |f|
  = render 'company_data', f: f

Upvotes: 3

Views: 400

Answers (3)

AnthonyM.
AnthonyM.

Reputation: 369

I went with a modified version of Alex's answer.

def something
  # TODO: @profile =
    
  respond_to do |format|
    format.turbo_stream do
      render turbo_stream: turbo_stream.update(
        :profile_company_data, partial: "company_data", locals: {f: profile_form_object}
      )
    end
  end
end

def profile_form_object
  helpers.fields model: @profile do |f|
    return f
  end
end

This way I can reuse the profile_form_object in other controller actions.

# ledger_items/_form.html.haml
= form_with model: ledger_item, do |f|
  = f.label :account_name, class: "form-label"
  = f.text_field :account_name, class: "form-control"

  = f.label :expense_code_id, class: "form-label"
  = render "ledger_items/expense_code_field", f: f

I only needed to replace a select box to change the expense_code options, so I put that field in it's own partial.

# ledger_items/_expense_code_field.html.haml
= f.select :expense_code_id,
  grouped_options_for_select(ExpenseCategory.select_code_options(current_company),
  f.object.expense_code_id), { include_blank: '- Please select -' },
  { id: 'ledger_item_expense_code_id', class: "form-select"}

Then in my controller

# some_action 
format.turbo_stream do
  render turbo_stream: [
    turbo_stream.replace("ledger_item_expense_code_id",
      partial: "ledger_items/expense_code_field",
      locals: { current_company: current_company, f: ledger_item_form_object }
    )
  ]
end

def ledger_item_form_object
  helpers.fields model: @ledger_item do |f|
    return f
  end
end

Notice I didn't need to use a turbo_frame_tag to replace the select box, just the select box ID.

Upvotes: 0

yungindigo
yungindigo

Reputation: 281

You can use the fields method instead of form_with

def add_nested_field
  @profile = current_user.profile
end

// add_nested_field.turbo_stream.erb
<%= turbo_stream.update("profile_company_data") do %>
  <%= fields model: @profile do |f| %>
    <%= render 'company_data', f: f %>
  <% end %>
<% end %>

This will generate the fields with correct naming for params but not add the csrf token, etc.. that form_with does.

Upvotes: 2

Alex
Alex

Reputation: 30036

You can do it straight from controller:

def something
  # TODO: @profile =

  respond_to do |format|
    format.turbo_stream do
      helpers.fields model: @profile do |f|
        render turbo_stream: turbo_stream.update(
          :profile_company_data, partial: "company_data", locals: {f:}
        )
      end
    end
  end
end

onClick -> 'fetch_company_data()' that will need to be a TURBO_STREAM request: https://stackoverflow.com/a/75579188/207090

Do you actually need turbo_frame_tag there?

Note that replace will replace the <turbo-frame> with whatever is rendered by the partial. But you probably want to keep it.

Upvotes: 2

Related Questions