Partially generating dynamic child strings in Rails

I applied a nested attribute form using the Railscast nested attributes as a guide. As a result, the user can click the icon to dynamically add “child” lines to my view.

Unfortunately, I can only do this job for the last icon in my view (shown here here ). This icon is generated in my view, and the rest are generated in partial, which is used to render each line.

Can this be done? If so, what is the best approach?

Here is my last attempt.

Sheet has_many Slots. In the sheet editing view, I use the sheet form builder to render the partial fragment and also pass it to the link_to_add_fields , which displays a link that will generate a new line when clicked (this part works fine), you will notice that I'm also trying to pass sheet partial so that I can call link_to_add_fields from there, but here it breaks down:

View - edit.html.haml :

 = sheet.fields_for :slots do |builder| = render 'slots/edit_fields', f: builder, sheet:sheet = link_to_add_fields image_tag("plus.jpg", size:"18x18", alt:"Plus"), sheet, :slots, 'slots/edit' 

Partial - _edit_fields.html.haml :

 - random_id = SecureRandom.uuid .row.signup{:id => "edit-slot-#{random_id}"} .col-md-1 %span.plus-icon = link_to_add_fields image_tag("plus.jpg", size:"18x18", alt:"Plus"), sheet, :slots, 'slots/edit' %span.minus-icon = image_tag "minus.jpg", size:"18x18", alt:"Minus" .col-md-2= f.text_field :label ... other fields ... 

Helper Method:

 def link_to_add_fields(name, f, association, partial) new_object = f.object.send(association).klass.new id = new_object.object_id fields = f.fields_for(association, new_object, child_index: id) do |builder| render(partial.to_s.singularize + "_fields", f: builder, name: name) end link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")}) end 

I get an undefined local variable or method 'sheet' in the helper call from partial. Basically, I need a sheet (parent) form builder that will be available on every link for the assistant to work. Or I need to abandon this approach and use AJAX (also tried this).

UPDATE

After debugging the bit, it is clear that sheet is passed to partial. The root problem is that I seem to set up infinite recursion:

  • Partial calls link_to_add_fields , so the + icon can serve as an add child link.
  • link_to_add_fields displays partial so that fields can be generated when you click the + icon.

Another problem that I am facing is that when the original children are displayed, they get sequential indexes in the attribute collection (0, 1, 2, ...). That way, even if I figure out a way to visualize new child lines among the originals, I'm not sure how I can maintain the order of the children when the form is submitted without much jQuery charging or something like that.

+6
source share
3 answers

I decided to solve this problem using jQuery. Not super elegant, but very effective. Basically, I just added a click handler for the + icons, which built a new line and inserted it where needed (basically just cracked the jQuery needed to replicate the HTML created by Rails). In case my use case helps someone else in the future, here is jQuery (see the imgur link in the original post for reference):

 //click handler to add edit row when user clicks on the plus icon $(document).on('click', '.plus-icon', function (event) { //build a new row var one_day = 24 * 3600 * 1000; var d = new Date(); var unique_id = d.getTime() % one_day + 10000; // unique within a day and offset past original IDs var new_div = $('<div/>', {'class': 'row signup'}).append( $('<div/>', {'class': 'col-md-1'}).append( $('<span/>', {'class': 'plus-icon'}).append( '<img alt="Plus" src="/assets/plus.jpg" width="18" height="18" />' ) ).append( $('<span/>', {'class': 'minus-icon'}).append( '<img alt="Minus" src="/assets/minus.jpg" width="18" height="18" />' ) ) ).append( $('<div/>', {'class': 'col-md-2'}).append( '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][label]" id="sheet_slots_attributes_'+unique_id+'_label">' ) ).append( $('<div/>', {'class': 'col-md-2'}).append( '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][name]" id="sheet_slots_attributes_'+unique_id+'_name">' ) ).append( $('<div/>', {'class': 'col-md-2'}).append( '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][email]" id="sheet_slots_attributes_'+unique_id+'_email">' ) ).append( $('<div/>', {'class': 'col-md-2'}).append( '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][phone]" id="sheet_slots_attributes_'+unique_id+'_phone">' ) ).append( $('<div/>', {'class': 'col-md-2'}).append( '<input type="text" value="" name="sheet[slots_attributes]['+unique_id+'][comments]" id="sheet_slots_attributes_'+unique_id+'_comments">' ) ); var new_input = '<input id="sheet_slots_attributes_'+unique_id+'_id" type="hidden" name="sheet[slots_attributes]['+unique_id+'][id]" value="'+unique_id+'">'; //insert new row before clicked row $(new_div).insertBefore($(this).parent().parent()); $(new_input).insertBefore($(this).parent().parent()); }); 

I’ve been stuck here for a while ... a reminder that there are usually several ways to solve a problem, and it’s important not to focus on one idea. Thank you all for your suggestions and materials.

+3
source

undefined local variable or method 'sheet'

Called by how you perform partial.

= render 'slots/edit_fields', f: builder, sheet:sheet

Not enough to pass variables to partial. You need:

= render partial: 'slots/edit_fields', locals: {f: builder, sheet:sheet}

This will make f available in your partial.

+3
source

This sounds surprisingly close to a problem that I had to solve a few months ago.

If I have this correct, your user has a list of "things" and you want your user to be able to add another "thing" using the displayed form.

I decided it like that. My “thing” is empires, users can change the name, delete an empire or add a new empire in the context of managing their personal data, for example, change their email address and passwords.

 <%= form_for @user do |f| %> <%= render layout: "users/partials/fields", locals: {f: f} do %> <b>Empires</b><br> <% n = 0 %> <%= f.fields_for :empires do |empire_form, index| %> <% n += 1 %> <%= empire_form.label :name, class: 'ms-indent' %> <%= empire_form.text_field :name, class: 'form_control' %> <%= empire_form.label :_destroy, 'Delete' %> <%= empire_form.check_box :_destroy %> <br> <% end %> <!-- Empty field for new Empire --> <b class='ms-indent'>Name</b> <input class="form_control" value="" name="user[empires_attributes][<%=n+1%>][name]" id="user_empires_attributes_0_name" type="text"> <b>new</b> <br> <% end %> <%= f.submit "Save changes", class: "btn btn-primary" %> <% end %> 

An interesting bit is a counter to get the number of existing empires (I don’t remember why I didn’t just use size or length), and using "n + 1" in the html input field to include a new empire in the form presented, with its correct identifier . Note the use of the html input tag, Rails has no equivalent.

[Just an additional note in the light of Dickey Bays's comment. This "n + 1" approach works, but can be replaced with a random number and still work. I'm going to experiment with this later, but it works, so I'm in no hurry.]

Partial "fields" look like this, pay attention to the "exit" in the middle.

 <%= f.label :name %> <%= f.text_field :name, class: 'form-control', autofocus: true %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= yield %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control', value: "" %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> 

Partially, the "fields" include a do-end block containing my empires code. Partial includes this block, where it indicates "exit".

Finally, the update controller code.

 def update @user = User.find(params[:id]) updated = false begin updated = @user.update_attributes(user_params) rescue ActiveRecord::DeleteRestrictionError flash[:warning] = "Empire contains ships. Delete these first." end if updated flash[:success] = "Profile updated" redirect_to @user else render 'edit' end end 

I will not pretend that this is the only solution, and not even necessarily the best. But it took several days to get rid of (Google and StackOverflow I did not succeed!), And since then it has worked reliably. The key was to really give up trying to find a Rails solution to add a new empire and just use html with a new identifier for the new empire. Perhaps you can improve it. Hope this helps.

+1
source

Source: https://habr.com/ru/post/986829/


All Articles