#70 Nested Forms from Scratch
3-5-2017

Summary

Learn how to handle multiple models in a single form with accepts_nested_attributes_for and learn how to add and remove nested records through JavaScript.
7
rails form javascript 9:48

Summary

Terminalrails g scaffold todo_list name
rails g model task todo_list:references name completed:boolean due:date
models/todo_list.rbclass TodoList < ApplicationRecord
  has_many :tasks, dependent: :destroy
  accepts_nested_attributes_for :tasks, allow_destroy: true, reject_if: proc { |att| att['name'].blank? }
  # accepts_nested_attributes_for :tasks, allow_destroy: true, reject_if: :all_blank
end
models/task.rbclass Task < ApplicationRecord
  belongs_to :todo_list, optional: true
end
helpers/application_helper.rbmodule ApplicationHelper
  def link_to_add_row(name, f, association, **args)
    new_object = f.object.send(association).klass.new
    id = new_object.object_id
    fields = f.simple_fields_for(association, new_object, child_index: id) do |builder|
      render(association.to_s.singularize, f: builder)
    end
    link_to(name, '#', class: "add_fields " + args[:class], data: {id: id, fields: fields.gsub("\n", "")})
  end
end
application.js$(document).on('turbolinks:load', function() {

  $('form').on('click', '.remove_record', function(event) {
    $(this).prev('input[type=hidden]').val('1');
    $(this).closest('tr').hide();
    return event.preventDefault();
  });

  $('form').on('click', '.add_fields', function(event) {
    var regexp, time;
    time = new Date().getTime();
    regexp = new RegExp($(this).data('id'), 'g');
    $('.fields').append($(this).data('fields').replace(regexp, time));
    return event.preventDefault();
  });
  
});
_form.html.erb
<%= simple_form_for(@todo_list) do |f| %> <%= f.error_notification %>   <div class="form-inputs">     <%= f.input :name %>   </div>   <table class='table'>     <thead>       <tr>         <th></th>         <th>Task Name</th>         <th>Completed</th>         <th>Due</th>       </tr>     </thead>     <tbody class='fields'>       <%= f.simple_fields_for :tasks do |builder| %>         <%= render 'task', f: builder %>       <% end %>     </tbody>   </table>   <div class="form-actions">     <%= f.button :submit %>     <%= link_to_add_row('Add Task', f, :tasks, class: 'btn btn-primary') %>   </div> <% end %>
_task.html.erb

<tr>   <td>     <%= f.input_field :_destroy, as: :hidden %>     <%= link_to 'Delete', '#', class: 'remove_record' %>   </td>   <td><%= f.input :name, label: false %></td>   <td><%= f.input :completed, label: false %></td>   <td><%= f.input :due, label: false, as: :string %></td> </tr>

Simon Kiteley PRO said over 1 year ago:

I'm really torn to whether this approach gives a good user experience. Its so easy to hit back or refresh the browser and loose what you have entered. I tend to use ajax and save as I go along. Wonder what other think?

Is a great tutorial though :)

kobaltz PRO said over 1 year ago:

From a UX perspective, it is consistent with how, by default, other forms work. You're able to refresh the browser or hit the back button to navigate away from normal forms without warning and losing any field changes.

My approach is usually to provide some form validations via javascript. So if the user adds a record or if they modify existing fields on the page, set a variable in JS. If the user navigates away from the page before saving, then show them an alert to prevent leaving the page on accident.

Mike Belyakov said over 1 year ago:

Good video!

The line

$(this).prev('input[type=hidden]').val('1');

will work good only of the form have 1 hidden field. So, better to fetch _delete input with other selector

aramsm said over 1 year ago:

Hello guys,


Im trying to build a dinamyc form for the same ToDo app, with the difference that it has Devise making user auth system. The thing is I cant use coffe.js, cocoon, SimpleForm or any other gem (except for jQuery). So my form is not working and I dont know what is causing it (clicking on add does nothing).


My project is: https://github.com/aramsm/miniapp-autoseg

c.marroquin2106 PRO said 6 months ago:

Great video. Helped me a lot.

Could you please consider to create a video about nested forms but with Many to Many association. Same functionality from scratch and also with coccon. Ithink something like that could solve a lot of question in stackoverflow hahaha.

For example, you have address and you have your categories table and your products table. This could be a many to many association and how to add several products when you are creating a category if you have your category_products table. Of course you have your scaffolds for categories and products in case you want to create separate.

Thanks again.

Login to Comment