Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Since nobody has offered an answer to this, even after a bounty, I've finally managed to get this working myself. This wasn't supposed to be a stumper! Hopefully this will be easier to do in Rails 3.0.</p> <p>Andy's example is a good way of deleting records directly, without submitting a form to the server. In this particular case, what I'm really looking for is a way to dynamically add/remove fields before doing an update to a nested form. This is a slightly different case, because as the fields are removed, they aren't actually deleted until the form is submitted. I will probably end up using both depending on the situation.</p> <p>I've based my implementation on <a href="http://github.com/timriley/complex-form-examples/" rel="noreferrer">Tim Riley's complex-forms-examples</a> fork on github.</p> <p>First set up the models, and make sure they support nested attributes:</p> <pre><code>class Person &lt; ActiveRecord::Base has_many :phone_numbers, :dependent =&gt; :destroy accepts_nested_attributes_for :phone_numbers, :reject_if =&gt; lambda { |p| p.values.all?(&amp;:blank?) }, :allow_destroy =&gt; true end class PhoneNumber &lt; ActiveRecord::Base belongs_to :person end </code></pre> <p>Create a partial view for the PhoneNumber's form fields:</p> <pre><code>&lt;div class="fields"&gt; &lt;%= f.text_field :description %&gt; &lt;%= f.text_field :number %&gt; &lt;/div&gt; </code></pre> <p>Next write a basic edit view for the Person model:</p> <pre><code>&lt;% form_for @person, :builder =&gt; LabeledFormBuilder do |f| -%&gt; &lt;%= f.text_field :name %&gt; &lt;%= f.text_field :email %&gt; &lt;% f.fields_for :phone_numbers do |ph| -%&gt; &lt;%= render :partial =&gt; 'phone_number', :locals =&gt; { :f =&gt; ph } %&gt; &lt;% end -%&gt; &lt;%= f.submit "Save" %&gt; &lt;% end -%&gt; </code></pre> <p>This will work by creating a set of template fields for the PhoneNumber model that we can duplicate with javascript. We'll create helper methods in <code>app/helpers/application_helper.rb</code> for this:</p> <pre><code>def new_child_fields_template(form_builder, association, options = {}) options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new options[:partial] ||= association.to_s.singularize options[:form_builder_local] ||= :f content_tag(:div, :id =&gt; "#{association}_fields_template", :style =&gt; "display: none") do form_builder.fields_for(association, options[:object], :child_index =&gt; "new_#{association}") do |f| render(:partial =&gt; options[:partial], :locals =&gt; { options[:form_builder_local] =&gt; f }) end end end def add_child_link(name, association) link_to(name, "javascript:void(0)", :class =&gt; "add_child", :"data-association" =&gt; association) end def remove_child_link(name, f) f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", :class =&gt; "remove_child") end </code></pre> <p>Now add these helper methods to the edit partial:</p> <pre><code>&lt;% form_for @person, :builder =&gt; LabeledFormBuilder do |f| -%&gt; &lt;%= f.text_field :name %&gt; &lt;%= f.text_field :email %&gt; &lt;% f.fields_for :phone_numbers do |ph| -%&gt; &lt;%= render :partial =&gt; 'phone_number', :locals =&gt; { :f =&gt; ph } %&gt; &lt;% end -%&gt; &lt;p&gt;&lt;%= add_child_link "New Phone Number", :phone_numbers %&gt;&lt;/p&gt; &lt;%= new_child_fields_template f, :phone_numbers %&gt; &lt;%= f.submit "Save" %&gt; &lt;% end -%&gt; </code></pre> <p>You now have the js templating done. It will submit a blank template for each association, but the <code>:reject_if</code> clause in the model will discard them, leaving only the user-created fields. <strong>Update:</strong> <em>I've rethought this design, see below.</em></p> <p>This isn't truly AJAX, since there isn't any communication going on to the server beyond the page load and form submit, but I honestly could not find a way to do it after the fact.</p> <p>In fact this may provide a better user experience than AJAX, since you don't have to wait for a server response for each additional field until you're done.</p> <p>Finally we need to wire this up with javascript. Add the following to your `public/javascripts/application.js' file:</p> <pre><code>$(function() { $('form a.add_child').click(function() { var association = $(this).attr('data-association'); var template = $('#' + association + '_fields_template').html(); var regexp = new RegExp('new_' + association, 'g'); var new_id = new Date().getTime(); $(this).parent().before(template.replace(regexp, new_id)); return false; }); $('form a.remove_child').live('click', function() { var hidden_field = $(this).prev('input[type=hidden]')[0]; if(hidden_field) { hidden_field.value = '1'; } $(this).parents('.fields').hide(); return false; }); }); </code></pre> <p>By this time you should have a barebones dynamic form! The javascript here is really simple, and could easily be done with other frameworks. You could easily replace my <code>application.js</code> code with prototype + lowpro for instance. The basic idea is that you're not embedding gigantic javascript functions into your markup, and you don't have to write tedious <code>phone_numbers=()</code> functions in your models. Everything just works. Hooray!</p> <hr> <p>After some further testing, I've concluded that the templates need to be moved out of the <code>&lt;form&gt;</code> fields. Keeping them there means they get sent back to the server with the rest of the form, and that just creates headaches later.</p> <p>I've added this to the bottom of my layout:</p> <pre><code>&lt;div id="jstemplates"&gt; &lt;%= yield :jstemplates %&gt; &lt;/div </code></pre> <p>And modified the <code>new_child_fields_template</code> helper:</p> <pre><code>def new_child_fields_template(form_builder, association, options = {}) options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new options[:partial] ||= association.to_s.singularize options[:form_builder_local] ||= :f content_for :jstemplates do content_tag(:div, :id =&gt; "#{association}_fields_template", :style =&gt; "display: none") do form_builder.fields_for(association, options[:object], :child_index =&gt; "new_#{association}") do |f| render(:partial =&gt; options[:partial], :locals =&gt; { options[:form_builder_local] =&gt; f }) end end end end </code></pre> <p>Now you can remove the <code>:reject_if</code> clauses from your models and stop worrying about the templates being sent back.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload