What would be a good approach to unlimited nested add / edit forms in a rails / scaffold application?

Overview / Models

Say, for example, you have a rather large system with fairly universal models (we will use three for this example)

  • Location (general address fields, type, line1, line2, city, etc.)
  • Companies (general company fields, type, name, etc.)
  • Contacts (general contact fields, tpye, name, jobtitle, etc.)

Example (+ screenshot rails_admin)

I find that when creating such systems I always encounter the same problem. For example, I click the "Add" button in the company (so I’ve now downloaded part of the form, etc.), I partially explain how to add the company and find that I need a place not in the system. I want to quickly open a modal window, add a location, and then update the selection through jquery or something in that direction. Great, nothing complicated was done on systems like rails_admin (see screenshot below):

http://www.server1-breakfrom.com/nestedaddexample.jpg

This is good when you are dealing with one level of nesting and, generally speaking, all is well when dealing with this as a rarity (since you can program in the ability how and when). Nevertheless, I am creating a system in which it should almost be the basis in itself, since I need it in more than 50% of the models. I need the ability to dynamically add various parameters to the model / controller and have forms dynamically generating the corresponding buttons.

Additional issues

  • Modalities in / above the models - when adding a contact you click to add your company, in this new company add the form you want to add: boom, modal over modal.
  • What text in jquery is added to the selection - you need to know how to update the select element and possibly find all the relevant selection elements on the screen. Adding a company will use an identifier and possibly a name, but for a location, you will need to use an ID, as well as, possibly, line 1, line 2 and the city area for text.
  • The check is in modal (but I assume that we can use some kind of jquery, since we are already very dependent on it)

Question Deployment

So, in addition to the question: am I complicating things too much, and is there an opportunity to solve my problem (I must emphasize that 3 models are just examples, I have many models that should refer to a company, for example, this is not just a contact , so "just program it as you need" will not work!).

Or, should I just parse rails_admin and pull out the bit I need? (Again, they did not solve the problem of multiple nesting, so I feel that starting from scratch could be better?).

+4
source share
2 answers

Solution

After much testing and going through the code kindly provided by RadBrad, I accidentally stumbled upon Gem with an answer to almost what I expected from the question / phrase "dynamic nested forms (with jQuery)". “Unlimited” seems to have been a poor word choice for the original question, but hopefully this text will pose a question that will be slightly improved on search engines.

Gemstone

This gem is called Cocoon and can be found here: https://github.com/nathanvda/cocoon

Additionally

As a bonus, it is built using jQuery, compatible with simple_form, formtastic and also fits perfectly with Twitter Bootstrap!

Additional notes

I did not fully test it and was honestly honest, the verification already looks like a minor problem, but I will update this answer further if I find something that does not work properly.

Cocoon's sample github projects are pretty outdated, and you will need to twist a lot in the Gem file to get them working (but it is really possible!). Be sure that the main gem (as of this writing) is relevant.

+1
source

I faced a similar situation. My solution may have performance issues in your scenario, this is not for me, but it is a low concurrency system, only 300 users.

What I did was with one kind / form of MEGA AJAX! and use javascript to show / hide if necessary. This method requires that you do not use form tag helpers (i.e. Text_field_tag ​​instead of f.text_field), you must take control of the element names.

So, start creating a single view with all the forms that you ultimately need. You will have to distinguish between them, so put each in a div element with a unique identifier.

<%= form_for @mega, :remote=>true do %> <div id='main_part'> <%= render :partial => "main_part", :object=>@mega %> </div <div id='subpart1'> <%= render :partial => "subpart1" , :object=>@foobar , :locals=>{:id=>@foobar.id} %> </div <div id='subpart2'> <%= render :partial => "subpart2" , :object=>@barfoo , :locals=>{:id=>@barfoo.id} %> </div <% end %> 

An example of partial parts of a form to distinguish between submit buttons:

 <%= label_tag "main_part[name]" ,"Name" %> <%= text_field_tag "main_part[name]" , main_part.name %> <%= submit_tag "UPDATE", :name=>'main_part' %> <%= hidden_tag_field "subpart1_id" , id %> <%= label_tag "subpart1[city]" ,"City" %> <%= text_field_tag "subpart1[city]" , subpart1.city%> <%= submit_tag "ADD", :name=>'subpart1' %> 

So, now you need a mega-controller, because this one form is sent to one controller action. This controller will look like a regular controller only for your top-level model, but for ALL models you will need to complete the household.

This controller action should determine which submit button is pressed, i.e.

 def update if params[:main_part] # since I controlled the parameter naming I know what in params[:main_part] # which is main_part[:name] @mega = MainThing.find(params[:id]) @mega.attributes = params[:main_part] @mega.save # only for main_part is the id valid, in every other case you have to # manually extract the id elsif params[:subpart1] @subpart1_id = params[:subpart1_id] @foobar = FooBar.find(@subpart1_id) @foobar.attrubutes = params[:subpart1] @foobar.save else end end 

Since the mega form is remote => true, you need to create a javascript file that will reload all your partial parts of the form, so in app / views / megas / update.js.erb:

 $('#main_part').html('<%= escape_javascript(render :partial=> "main_part", :object=>@mega) %>'); $('#subpart1').html('<%= escape_javascript(render :partial=> "subpart1", :object=>@foobar :locals=>{:id=>@foobar.id) %>'); 

Now, this is where performance issues arise. If you notice that if I run this javascript, it will expect all these instance variables to be defined, so the various partial ones will display update select tags that received new values ​​as a result of the update. In my case, I just load them all into a filter before doing it, i.e.

 class MegaController < ApplicationController before_filter :load, :only=>[:edit] def load @mega = Mega.find(params[:id]) @foobar = @mega.foobar @barfoo = @foobar.barfoo end 

But you also could NOT do this, but instead create separate javascript files and render them specifically in the controller, i.e.

 def update if params[:main_part] # do whatever... render :action=>'update_set1', :handler=>[:erb], :formats=>[:js] elsif params[:subpart1] # do whatever render :action=>'update_set1', :handler=>[:erb], :formats=>[:js] elsif params[:subpart2] # do whatever render :action=>'update_set2', :handler=>[:erb], :formats=>[:js] end end 

The app / views / mega / update_set1.js.erb file will only update partial files affected by the upgrade to @mega or @foobar, update_set2.js.erb will update the partial files affected by the upgrade to @barfoo.

Last point, you are the form remote => true, how do you exit? Assuming you have:

 <%= submit_tag 'Cancel', :name=>'cancel' %> 

Then in the controller you will do something like:

  def update if params[:cancel] render :js=> "window.location = '/'" else # whatever.... end end 

The final step is to add javascript to show / hide the divs form as needed, an exercise left to the reader ....

UPDATE

 class Mega < ActiveRecord::Base def self.get_param_name self.class.name end def self.get_id_name "#{self.class.name}_id" end end class MyModel < Mega end 

then in the mega-controller:

 def edit @mymodel = MyModel.find(params[MyModel.get_id_name]) end 
+2
source

All Articles