/ code

Dynamic Input Fields in Rails

My friend and I are in the middle of creating an app that allows a user to create polls. The first problem I ran into was making the choice fields dynamic. We wanted it so users can add or remove fields as they please.

Here is what I came up with:

Setup

Getting this working is pretty simple. Here is how the form should look.

<%= form_for(@poll, remote: true, format: :json, html: {class: :poll_form} ) do |f| %>

  <div id="error_explanation" style='display:none;'>
    <ul>
      <% if @poll.errors.any? %>
        <% @poll.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      <% end %>
    </ul>
  </div>

  <div class="fields">
    <%= f.label :title %><br>
    <%= f.text_field :title %><br>
    <div id="poll_choices">
        <%= render 'poll_choice' %>
        <%= render 'poll_choice' %>
        <%= render 'poll_choice' %>
        <%= render 'poll_choice' %>
    </div>
    <a href="javascript:;" id="addNewChoice">Add New Choice</a>
  </div>

  <div class="actions">
    <%= hidden_field_tag :user_id, current_user.id %>
    <%= f.submit %>
  </div>

<% end %>

<div class="success-message">
</div>

<div style="display: none;" id="new_choice_form">
    <%= render partial: "poll_choice", locals: {poll: false} %>
</div>

First thing you'll notice is we are making this form submit its action remotely, as specified with remote: true, format: :json in the form_for. This is not required. It's there purely to make the poll creation process seem smoother by not having the user be redirected after clicking Create Poll.

There are 2 important things added to this form that may require clarification:


Whats with all of those partials?

<div id="poll_choices">
        <%= render 'poll_choice' %>
        <%= render 'poll_choice' %>
        <%= render 'poll_choice' %>
        <%= render 'poll_choice' %>
</div>

This simply renders our poll_choice partial 4 times because we wanted there to be 4 choice fields added automatically.

Let's dive deeper into this code and see what that partial actually contains.

<div class="choiceForm">
     <%= label_tag "Choice" %>
     <br>
     <%= text_field_tag "choices[]" %> <a href="#" onclick="removeChoice($(this))" class="remove-choice" > X </a>
     <br>
</div>

The most important part of this partial is the fact that the text field's name value contains [] in it. This tells Rails that there will be multiple choices, and to send that data to the controller as an array. That is just what we want. You can also see that I added a remove button that calls the function removeChoice when clicked. We'll get to that in a bit.


There is an invisible div at the bottom. What's with that?

This is where it actually gets interesting

<div style="display: none;" id="new_choice_form">
    <%= render partial: "poll_choice", locals: {poll: false} %>
</div>

Our Javascript (we'll get to it, don't worry) will take the contents of our invisible div, bring it back up to where our actual fields are being rendered, and display it. Easy & clever. Can't get better than that.


The Javascript Coffeescript

Watch how easy this is....

$("#addNewChoice").on "click", ->
    $("#poll_choices").append($("#new_choice_form").html())

Is your mind blown or what? Let's add our removeChoice function and we'll have fully dynamic fields.

@removeChoice = (element) ->
  element.parent().remove()

The Controller

After submitting, our controller is going to get params that look like this:

{"utf8"=>"✓",
"poll"=>{"title"=>"Should you comment below?"},
"choices"=>["Yes",
            "Most definitely",
            "No reason not to",
            "Only an idiot would say no",
            "Of Course"],
"user_id"=>"1",
"commit"=>"Create Poll"}

Notice how the choices are in a nice & manageable array!

The last thing we need to do is actually go through that array and save those objects. You should come up with a create method that looks something like this:


def create
    @poll = Poll.new(poll_params)
    choices_param = params[:choices]
    @choices = []
    choices_param.each.with_index(1) do |choice, index|
      choice = Choice.create(:text => choice, :location => index)
      @choices.push(choice)
    end
    @poll.choices = @choices
    @poll.user = User.find(params[:user_id])
    respond_to do |format|
      if @poll.save
        format.html { redirect_to @poll, notice: 'Poll was successfully created.' }
        format.json { render json: @poll }
      else
        format.html { render :new }
        format.json { render json: @poll.errors.full_messages, status: :unprocessable_entity }
      end
    end
end



There we go boys and girls. You should now be able to add and remove fields in your forms as you please thanks to partials and one or two lines of Javascript. Thanks for reading and tell me what you think of this technique below!