Tom Lehman
Tom Lehman

Reputation: 89173

Multiple forms for the same model in a single page

On the front page of my rap lyrics explanation site, there's a place where users can try explaining a challenging line:

alt text http://dl.dropbox.com/u/2792776/screenshots/2010-02-06_1620.png

Here's the partial I use to generate this:

<div class="stand_alone annotation" data-id="<%= annotation.id %>">
  <%= song_link(annotation.song, :class => :title) %>

  <span class="needs_exegesis"><%= annotation.referent.strip.gsub(/\n/, "\n <br />") %></span>

  <% form_for Feedback.new(:annotation_id => annotation.id, :created_by_id => current_user.try(:id), :email_address => current_user.try(:email)), :url => feedback_index_path, :live_validations => true do |f| %>
    <%= f.hidden_field :annotation_id %>
    <%= f.hidden_field :created_by_id %>
    <p style="margin-top: 1em">
        <%= f.text_area :body, :rows => 4, :style => 'width:96%', :example_text => "Enter your explanation" %>
    </p>
    <p>
      <% if current_user %>
        <%= f.hidden_field :email_address %>
      <% else %>
        <%= f.text_field :email_address, :example_text => "Your email address" %>
      <% end %>
      <%= f.submit "Submit", :class => :button, :style => 'margin-left: .1em;' %>
    </p>
  <% end %>
</div>

However, putting more than one of these on a single page is problematic because Rails automatically gives each form an ID of new_feedback, and each field an ID like feedback_body (leading to name collisions)

Obviously I could add something like :id => '' to the form and all its fields, but this seems a tad repetitive. What's the best way to do this?

Upvotes: 8

Views: 11187

Answers (5)

Daniel Rikowski
Daniel Rikowski

Reputation: 72494

If you don't want to change your input names or your model structure, you can use the id option to make your form ID unique and the namespace option to make your input IDs unique:

<%= form_for Feedback.new(...), 
    id: "annotation_#{annotation.id}_feedback"
    namespace: "annotation_#{annotation.id}" do |f| %>

That way our form ID is unique, i.e. annotation_2_feedback and this will also add a prefix, e.g. annotation_2_, to every input created through f.

Upvotes: 17

Damir Zekić
Damir Zekić

Reputation: 15940

For those stumbling here, looking for the solution for Rails 3.2 app, look at this question instead:

Rails: Using form_for multiple times (DOM ids)

Upvotes: 1

Shawn Regan
Shawn Regan

Reputation: 21

I also had the same issue but wanted a more extensible solution than adding the ID to each field. Since we're already using the form_for ... |f| notation the trick is to change the name of the model and you get a new HTML ID prefix.

Using a variant of this method: http://www.dzone.com/snippets/create-classes-runtime (I removed the &block stuff)

I create a new model that is an exact copy of the model I want a second form for on the same page. Then use that new model to render the new form.

If the first form is using

@address = Address.new

then

create_class('AddressNew', Address)
@address_new = AddressNew.new

Your ID prefix will be 'address_new_' instead of 'address_' for the second form of the same model. When you read the form params you can create an Address model to put the values into.

Upvotes: 2

Anurag
Anurag

Reputation: 141859

Did you consider nested_attributes for rails models? Instead of having multiple new feedback forms where each is tied to an annotation, you could have multiple edit annotation forms where each annotation includes fields for a new feedback. The id's of the generated forms would include the annotations id such as edit_annotation_16.

The annotation model would have a relationship to its feedbacks and will also accept nested attributes for them.

class Annotation < ActiveRecord::Base
  has_many :feedbacks
  accepts_nested_attributes_for :feedbacks
end

class Feedback < ActiveRecord::Base
  belongs_to :annotation
end

You could then add as many forms as you want, one for each annotation. For example, this is what I tried:

<% form_for @a do |form| %>
    Lyrics: <br />
    <%= form.text_field :lyrics %><br />
    <% form.fields_for :feedbacks do |feedback| %>
        Feedback: <br/>
        <%= feedback.text_field :response %><br />
    <% end %>
    <%= form.submit "Submit" %>
<% end %>

<% form_for @b do |form| %>
    Lyrics: <br />
    <%= form.text_field :lyrics %><br />
    <% form.fields_for :feedbacks do |feedback| %>
        Feedback: <br/>
        <%= feedback.text_field :response %><br />
    <% end %>
    <%= form.submit "Submit" %>
<% end %>

And the quick and dirty controller for the above edit view:

class AnnotationsController < ApplicationController
  def edit
    @a = Annotation.find(1)
    @a.feedbacks.build
    @b = Annotation.find(2)
    @b.feedbacks.build
  end

  def update
    @annotation = Annotation.find(params[:id])
    @annotation.update_attributes(params[:annotation])
    @annotation.save!
    render :index
  end
end

Upvotes: 6

Jimmy
Jimmy

Reputation: 37081

I had this same issue on a site I'm currently working on and went with the solution you mention at the bottom. It's not repetitive if you generate the ID programmatically and put the whole form in a partial. For example, on my site, I have multiple "entries" per page, each of which has two voting forms, one to vote up and one to vote down. The record ID for each entry is appended to the DOM ID of its vote forms to make it unique, like so (just shows the vote up button, the vote down button is similar):

<% form_for [entry, Vote.new], :html => { :id => 'new_up_vote_' + entry.id.to_s } do |f| -%>
  <%= f.hidden_field :up_vote, :value => 1, :id => 'vote_up_vote_' + entry.id.to_s %>
  <%= image_submit_tag('/images/icon_vote_up.png', :id => 'vote_up_vote_submit' + entry.id.to_s, :class => 'vote-button vote-up-button') %>
<% end -%>

Upvotes: 3

Related Questions