Rails 4:使用Cocoon Gem将child_index添加到动态添加(嵌套)的表单字段中 [英] Rails 4: Adding child_index to dynamically added (nested) form fields with Cocoon Gem

查看:62
本文介绍了Rails 4:使用Cocoon Gem将child_index添加到动态添加(嵌套)的表单字段中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

已更新:我正在尝试将表单字段添加/删除到涉及多个模型的嵌套表单中.我看过Ryan Bates撰写的动态表单"栏目,并且提到了此使用茧宝石的文章.紧随该文章之后,除了child_index之外,其他所有内容都可以正常运行. child_index仅出现在第一个:kid输入字段(:name)和第一个:pet输入字段(:name:age)上.然后,它返回到要添加的字段的真实性令牌.

UPDATED: I am trying to add/remove form fields to a nested form involving multiple models. I have seen the "Dynamic Forms" railscast by Ryan Bates and I have referred to this article using the Cocoon Gem. Following that article has made everything work perfectly except for the child_index. The child_index is present only on the first :kid input field (:name) and the first :pet input fields (:name and :age). Then it goes back to an authenticity token for the fields being added.

我删除了所有JS和辅助方法,而是使用了一些内置于JS的Cocoon方法.

I've removed all of the JS and helper methods and instead I'm using some of the Cocoon methods that has built in JS.

我解决了以下问题:单击添加"会通过从application.html.erb文件中删除= javascript_include_tag :cocoon来添加两个字段而不是一个字段.

I fixed the problem where clicking "Add" would add two fields instead of one by removing the = javascript_include_tag :cocoon from the application.html.erb file.

我尝试添加jQuery和表单帮助器,但是不确定我是否正确输入了代码.

I have tried adding jQuery and form helpers but I'm not sure I entered the code correctly.

(我更改了模型对象以使关系更清楚)

(I have changed the model objects to make the relationships more clear)

parent.rb文件:

parent.rb file:

class Parent < ActiveRecord::Base

has_many :kids
has_many :pets, through: :kids # <<<<<< ADDED KIDS USING 'through:'

kid.rb文件:

class Kid < ActiveRecord::Base

belongs_to :parent
has_many :pets
accepts_nested_attributes_for :pets, reject_if: :all_blank, allow_destroy: true
validates :name, presence: true

pet.rb文件:

 class Pet < ActiveRecord::Base

 belongs_to :kid

 validates :name, presence: true

 validates :age, presence: true

这是我的_form.html.erb文件:

This is my _form.html.erb file:

 <%= form_for @parent do |f| %>
  <% if @parent.errors.any? %>
   <div class="alert alert-danger">
    <h3><%= pluralize(@student.errors.count, 'Error') %>: </h3>

         <ul>
            <% @student.errors.full_messages.each do |msg| %>
                <li><%= msg %></li>
            <% end %>
         </ul>
    </div>
 <% end %>

   <div class="inline">
     <div>
        <%= f.fields_for :kids do |kid| %>
         <%= render 'kid_fields', f: kid %>
        <% end %>
           <div>
            <%= link_to_add_association "Add Kid", f, :kids, id: 'add_kid',
            'data-association-insertion-method' => 'before',
            'data-association-insertion-traversal' => 'closest' %>
           </div>
        <% end %>   
     </div>


    </div>
        <div class="form-actions">
            <%= f.submit 'Create Parent', class: 'btn btn-primary' %>
        </div>

<% end %>

这是我的_kid_fields.rb文件:

This is my _kid_fields.rb file:

    <div class="nested-fields">

     <div class="kid-fields inline">
      <%= f.hidden_field :_destroy, class: 'removable' %>
      <%= f.text_field :name, class: 'form-control', placeholder: 'Kid's Name', id: 'kid-input' %>
        <div>
         <%= link_to_remove_association 'Remove Kid', f %>
        </div>


        <%= f.fields_for :pets do |pet| %>
         <%= render 'pet_fields', f: pet %>
        <% end %>
      </div>    
      <div>
       <%= link_to_add_association "Add Pet", f, :pets, id: 'add_pet',
            'data-association-insertion-method' => 'before' %>
      </div>
    </div>

这是我的_pet_fields.rb文件:

This is my _pet_fields.rb file:

    <div class="nested-fields">
     <div class="pet-fields">
      <%= f.hidden_field :_destroy, class: 'removable' %>
      <%= f.text_field :name, placeholder: 'Pet Name', id: 'pet-name-input' %>
      <%= f.text_field :age, placeholder: 'Pet Age', id: 'pet-age-input' %>  
      <%= link_to_remove_association 'Remove Pet', f, id: 'remove_pet' %>
     </div>  
    </div>

推荐答案

当我单击删除学生"时,它会删除该链接上方的每个字段

when I click the "Remove Student" it removes every field above that link

这是您所关注的特定RailsCast的一个众所周知的问题(已过时).还有另一个此处:

This is a well known issue with the particular RailsCast you're following (it's outdated). There's another here:

问题归结于 child_index href ="https://stackoverflow.com/questions/2879208/rails-fields-for-child-index-option-explanation"> fields_for参考.

The problem comes down to the child_index of the fields_for references.

每次使用fields_for(这就是您使用上述javascript功能复制的内容)时,都会为其创建的每个字段集分配一个id.这些idsparams中用于分隔不同的属性.还将它们作为 HTML"id"属性分配给每个字段.

Each time you use fields_for (which is what you're replicating with the above javascript functionality), it assigns an id to each set of fields it creates. These ids are used in the params to separate the different attributes; they're also assigned to each field as an HTML "id" property.

因此,您遇到的问题是,由于您每次添加新字段时都不会更新此child_index,因此它们都是相同的.而且,由于您的link_to_add_fields助手不会更新JS(IE允许您添加具有完全相同的child_index的字段),因此,这意味着每当您删除"一个字段时,它将全部选择它们.

Thus, the problem you have is that since you're not updating this child_index each time you add a new field, they're all the same. And since your link_to_add_fields helper does not update the JS (IE allows you to append fields with exactly the same child_index), this means that whenever you "remove" a field, it will select all of them.

此问题的解决方法是设置child_index(下面将向您解释).

The fix for this is to set the child_index (I'll give you an explanation below).

老实说,我希望给您新的代码,而不是挑剔您过时的内容.

I'd prefer to give you new code than to pick through your outdated stuff to be honest.

我在这里写了这个(尽管可以稍微抛光一下): 轨道与f.fields_for和AJAX一起使用accepts_nested_attributes_for

I wrote about this here (although it could be polished a little): Rails accepts_nested_attributes_for with f.fields_for and AJAX

有些宝石可以为您做到这一点-一种叫做 Cocoon 很受欢迎,尽管不是即插即用"解决方案,很多人认为是这样.

There are gems which do this for you - one called Cocoon is very popular, although not a "plug and play" solution many think it is.

尽管如此,即使您确实选择使用Cocoon ...

Nonetheless, it's best to know it all works, even if you do opt to use something like Cocoon...

fields_for

要了解该解决方案,您必须记住Rails创建了 HTML 表单.

To understand the solution, you must remember that Rails creates HTML forms.

您可能知道这一点;许多没有.

You know this probably; many don't.

这很重要,因为当您意识到 HTML 表单必须遵守 HTML 施加的所有约束时,您将了解Rails并不是魔术师,人们似乎在想.

It's important because when you realize that HTML forms have to adhere to all the constraints imposed by HTML, you'll understand that Rails is not the magician a lot of folks seem to think.

创建嵌套"表单(添加/删除)功能的方法如下:

The way to create a "nested" form (without add/remove) functionality is as follows:

#app/models/student.rb
class Student < ActiveRecord::Base
   has_many :teachers
   accepts_nested_attributes_for :teachers #-> this is to PASS data, not receive
end

#app/models/teacher.rb
class Teacher < ActiveRecord::Base
   belongs_to :student
end

需要注意的一点是,您的 accepts_nested_attributes_for 应该是在 parent 模型上.也就是说,您要向其传递数据的模型(而不是接收数据的模型):

Something important to note is that your accepts_nested_attributes_for should be on the parent model. That is, the model you're passing data to (not the one receiving data):

嵌套属性可让您将属性保存在关联记录中 通过父母

Nested attributes allow you to save attributes on associated records through the parent

#app/controllers/students_controller.rb
class StudentsController < ApplicationController
   def new
      @student = Student.new
      @student.teachers.build #-> you have to build the associative object
   end

   def create
      @student = Student.new student_params
      @student.save
   end

   private

   def student_params
      params.require(:student).permit(:x, :y, teachers_attributes: [:z])
   end
end

通过内置这些对象,您可以在表单中使用它们:

With these objects built, you're able to use them in your form:

#app/views/students/new.html.erb
<%= form_for @student do |f| %>
   <%= f.fields_for :teachers |teacher| %>
       <% # this will replicate for as many times as you've "built" a new teacher object %>
        <%= teacher.text_field ... %>
   <% end %> 
   <%= f.submit %>
<% end %>

这是一个标准表格,它将数据发送到您的控制器,然后再发送到您的模型.模型中的accepts_nested_attributes_for方法会将嵌套的属性传递给从属模型.

This is a standard form which will send the data to your controller, and then to your model. The accepts_nested_attributes_for method in the model will pass the nested attributes to the dependent model.

-

与此相关的最好的事情是记下上面代码创建的嵌套字段的id.我手上没有任何例子.它应该显示嵌套字段的名称,例如teachers_attributes[0][name]等.

The best thing to do with this is to take note of the id for the nested fields the above code creates. I don't have any examples on hand; it should show you the nested fields have names like teachers_attributes[0][name] etc.

要注意的重要事项是[0]-这是 child_index ,它在所需的功能中起着至关重要的作用.

The important thing to note is the [0] - this is the child_index which plays a crucial role in the functionality you need.

动态

现在使用动态表单.

第一部分相对简单... 删除将字段从DOM中删除是一种情况.我们可以使用child_index,因此我们首先需要知道如何设置子索引等,等等.

The first part is relatively simple... removing a field is a case of deleting it from the DOM. We can use the child_index for that, so we first need to know how to set the child index etc etc etc...

#app/models/Student.rb
class Student < ActiveRecord::Base
    def self.build #-> non essential; only used to free up controller code
       student = self.new
       student.teachers.build
       student
    end
end

#app/controllers/students_controller.rb
class StudentsController < ApplicationController
   def new
      @student = Student.build
   end

   def add_teacher
      @student = Student.build
      render "add_teacher", layout: false
   end

   def create
      @student = Student.new student_params
      @student.save
   end

   private

   def student_params
      params.require(:student).permit(:x, :y, teachers_attributes: [:z])
   end
end

现在查看视图(请注意,您必须将表单分成部分):

Now for the views (note you have to split your form into partials):

#app/views/students/new.html.erb
<%= form_for @student do |f| %>
   <%= f.text_field :name %>
   <%= render "teacher_fields", locals: {f: f} %>
   <%= link_to "Add", "#", id: :add_teacher %>
   <%= f.submit %>
<% end %>

#app/views/_teacher_fields.html.erb
<%= f.fields_for :teachers, child_index: Time.now.to_i do |teacher| %>
   <%= teacher.text_field ....... %>
   <%= link_to "Remove", "#", id: :remove_teacher, data: {i: child_index} %>
<% end %>

#app/views/add_teacher.html.erb
<%= form_for @student, authenticity_token: false do |f| %>
   <%= render partial "teacher_fields", locals: {f:f}
<% end %>

应该为您呈现各种形式的表格,包括fields_for.注意child_index: Time.now.to_i -这为每个fields_for设置了唯一的ID,使我们能够根据需要在每个字段之间进行区分.

This should render the various forms etc for you, including the fields_for. Notice the child_index: Time.now.to_i -- this sets a unique ID for each fields_for, allowing us to differentiate between each field as you need.

使此 dynamic 生效,然后归结为JS:

Making this dynamic then comes down to JS:

#config/routes.rb
resources :students do 
   get :add_teacher, on: :collection #-> url.com/students/get_teacher
end

使用此路由使我们可以发送Ajax请求(以获取新字段):

Using this route allows us to send an Ajax request (to get a new field):

#app/assets/javascripts/.....coffee
$ ->

   #Add Teacher
   $(document).on "click", "#add_teacher", (e) ->
      e.preventDefault();

      #Ajax
      $.ajax
        url: '/students/add_teacher'
        success: (data) ->
           el_to_add = $(data).html()
           $('#subscribers').append(el_to_add)
        error: (data) ->
           alert "Sorry, There Was An Error!"

   #Remove Teacher
   $(document).on "click", "#remove_teacher", (e) ->
      e.preventDefault();

      id = $(this).data("i")
      $("input#" + i).remove()

这篇关于Rails 4:使用Cocoon Gem将child_index添加到动态添加(嵌套)的表单字段中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆