Dynamic form elements via AJAX

I’m trying to build a form with an ordered list of elements. You can
add/remove an arbitrary number of items. I can use link_to_remote to
insert a new element, but I am having problems maintaining order when it
comes to submitting the form. Here is my “element”:

  • "> <%= text_field_tag "choice_#{i}[text]", choice[0], :size => 16, :id => "choice_#{i}_text" %> <%= text_field_tag "choice_#{i}[value]", choice[1], :size => 4, :id => "choice_#{i}_value" %> <%= link_to_remote '+', :url => {:action => 'insert_choice', :id => "choice_#{i}" } %> <%= link_to_function '-', "Element.remove('choice_#{i}')" %>
  • It is actually a set of two text_fields, as you can see. When the form
    is initially built with a default of 3 “choices,” the order is given by
    choice_0, choice_1, etc. But when insert a new “choice”

  • via ajax it
    doesn’t have a proper name/id. I can call it “choice_new” or something
    but when the form is submitted the controller won’t know what order they
    were in the form.

    Any suggestions?

    -matthew

  • Hmmmmmmmmm. You could create a hash and populate it with a mapping of
    position to choice_id. You will need to generate a unique ID for each
    added choice… I would try storing the mapping and the ‘current ID’ in
    the session. Then, when you create a new choice:

    • Add one to the current ID in the session and use it for the choice
      ID. Save it to the session.
    • Get the hash from the session. Add the ID to it, pointing to the
      position. You will have to add one to all hash values greater than the
      position you want to insert to first.
    • Save the hash to the session.

    Deleting would be the same in reverse…

    Does that make sense?

    Ben

    Hmm, I never thought of storing the the choices in the session. So you
    would suggest redrawing the entire list each time a choice is added or
    deleted as opposed to trying to play it cheap and just insert/remote
    individual items?

    -matthew

    Matthew I. wrote:

    <%= link_to_remote ‘+’, :url => {:action => ‘insert_choice’, :id =>
    “choice_#{i}” } %>
    <%= link_to_function ‘-’, “Element.remove(‘choice_#{i}’)” %>

    It is actually a set of two text_fields, as you can see. When the form
    is initially built with a default of 3 “choices,” the order is given by
    choice_0, choice_1, etc. But when insert a new “choice”

  • via ajax it
    doesn’t have a proper name/id. I can call it “choice_new” or something
    but when the form is submitted the controller won’t know what order they
    were in the form.
  • I’d recommend changing to

    <% @choices = Array.new(3, Choice.new) if @choices.empty %>

      <% @choices.each do |choice| %>
    • <%= text_field_tag 'choice_text[]', choice.text, :size => 16 %> <%= text_field_tag 'choice_value[]', choice.value, :size => 4 %> <%= link_to_function '+', 'var c=this.parentNode;c.parentNode.insertBefore(c.cloneNode(true),c)' %> <%= link_to_function '-', 'var c=this.parentNode;c.parentNode.removeChild(c)' %>
    • <% end %>

    Now there’s no numbering or ids to maintain, the texts and values
    are each posted as arrays, respecting their order in the document,
    the client-side insertion and deletion of choices is much more
    responsive for the user, and you no longer need to include Prototype.

    However for efficiency in actual implementation the scripts
    should be made page-wide functions that are called from each link.


    We develop, watch us RoR, in numbers too big to ignore.

    Excellent. I wasn’t aware that I could use name[] as a field name.
    Solves all my problems. Well, now I am not sure that I really want to
    “clone” the node. I may have to put some extra JS in there to clear the
    text_field values on the new node.

    -matthew