Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add and remove nested model fields dynamically using Haml and Formtastic

Tags:

We've all seen the brilliant complex forms railscast where Ryan Bates explains how to dynamically add or remove nested objects within the parent object form using Javascript.

Has anyone got any ideas about how these methods need to be modified so as to work with Haml Formtastic?

To add some context here's a simplified version of the problem I'm currently facing:

# Teacher form (which has nested subject forms) [from my application]

- semantic_form_for(@teacher) do |form|   - form.inputs do     = form.input :first_name     = form.input :surname     = form.input :city     = render 'subject_fields', :form => form      = link_to_add_fields "Add Subject", form, :subjects    

# Individual Subject form partial [from my application]

- form.fields_for :subjects do |ff|    #subject_field     = ff.input :name     = ff.input :exam     = ff.input :level     = ff.hidden_field :_destroy     = link_to_remove_fields "Remove Subject", ff  

# Application Helper (straight from Railscasts)

  def link_to_remove_fields(name, f)     f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)")   end    def link_to_add_fields(name, f, association)     new_object = f.object.class.reflect_on_association(association).klass.new     fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|       render(association.to_s.singularize + "_fields", :f => builder)     end     link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}  \")"))   end 

#Application.js (straight from Railscasts)

  function remove_fields(link) {   $(link).previous("input[type=hidden]").value = "1";   $(link).up(".fields").hide();   }  function add_fields(link, association, content) {   var new_id = new Date().getTime();   var regexp = new RegExp("new_" + association, "g")   $(link).up().insert({     before: content.replace(regexp, new_id)   });   } 

The problem with implementation seems to be with the javascript methods - the DOM tree of a Formtastic form differs greatly from a regular rails form.

I've seen this question asked online a few times but haven't come across an answer yet - now you know that help will be appreciated by more than just me!

Jack

like image 501
Jack Kinsella Avatar asked Apr 20 '10 19:04

Jack Kinsella


2 Answers

You're on the right track:

...the DOM tree of a Formtastic form differs greatly from a regular rails form.

To adapt Ryan's example for formtastic, it's helpful to be reminded that the semantic_fields_for helper is similar to the semantic_form_for helper, which outputs the inputs in list form.

To keep things as close to the Railscast code as possible, you'll need to:

  • enclose the collection of nested fields in a wrapper (I use a div with the subjects CSS ID.)
  • enclose the nested fields in a ul/ol wrapper (I applied a nested-fields CSS class.)

Here's what your files should like.

Teacher form (with nested Subject fields):

- semantic_form_for(@teacher) do |form|   - form.inputs do     = form.input :first_name     = form.input :surname     = form.input :city      %h2 Subjects     #subjects       - form.semantic_fields_for :subjects do |builder|         = render :partial => "subject_fields", :locals => { :f => builder }       .links         = link_to_add_fields "Add Subject", form, :subjects 

Subject fields partial (for nested Subject):

%ul.nested-fields   = f.input :name   = f.input :exam   = f.input :level   = link_to_remove_fields "Remove Subject", f 

ApplicationHelper:

def link_to_remove_fields(name, f)   f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)") end  def link_to_add_fields(name, f, association)   new_object = f.object.class.reflect_on_association(association).klass.new   fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|     render(association.to_s.singularize + "_fields", :f => builder)   end   link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")")) end 

Application.js:

function remove_fields(link) {   $(link).previous("input[type=hidden]").value = "1";   $(link).up(".nested-fields").hide(); }  function add_fields(link, association, content) {   var new_id = new Date().getTime();   var regexp = new RegExp("new_" + association, "g")   $(link).up().insert({     before: content.replace(regexp, new_id)   }); } 

Using following gems:

  • formtastic (0.9.10)
  • haml (3.0.2)
  • gherkin (1.0.26)
  • rails (2.3.5)
like image 108
Bennett Avatar answered Sep 25 '22 02:09

Bennett


application.js for jQuery:

function remove_fields(link) {   $(link).prev("input[type=hidden]").val("1");   $(link).parent(".nested-fields").hide(); }  function add_fields(link, association, content) {   var new_id = new Date().getTime();   var regexp = new RegExp("new_" + association, "g")   $(link).parent().before(content.replace(regexp, new_id)); } 
like image 26
Steve Avatar answered Sep 26 '22 02:09

Steve