2015-10-07 9 views
7

AGGIORNATO: Sto tentando di aggiungere/rimuovere campi modulo a un modulo annidato che coinvolge più modelli. Ho visto il railscast "Dynamic Forms" di Ryan Bates e ho fatto riferimento a this article utilizzando lo Cocoon Gem. Seguendo questo articolo, tutto ha funzionato perfettamente, tranne che per child_index. Child_index è presente solo nel primo campo di immissione :kid (:name) e nei primi campi di immissione :pet (:name e :age). Quindi torna a un token di autenticità per i campi aggiunti.Rails 4: aggiunta child_index ai campi modulo aggiunti dinamicamente (nidificati) con Cocoon Gem

Ho rimosso tutti i metodi JS e helper e invece sto utilizzando alcuni dei metodi Cocoon che sono stati creati in JS.

Ho risolto il problema in cui facendo clic su "Aggiungi" aggiungerei due campi anziché uno rimuovendo = javascript_include_tag :cocoon dal file application.html.erb.

Ho provato ad aggiungere jQuery e form helper ma non sono sicuro di aver inserito il codice correttamente.

(ho cambiato gli oggetti del modello per rendere i rapporti più chiari)

file di parent.rb:

class Parent < ActiveRecord::Base 

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

lima kid.rb: file di

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 

T il suo è il mio file _form.html.erb:

<%= 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 %> 

Questo è il mio file _kid_fields.rb:

<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> 

Questo è il mio file _pet_fields.rb:

<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> 

risposta

7

quando Faccio clic su "Rimuovi studente" rimuove tutti i campi sopra quel collegamento

Questo è un problema ben noto con il particolare RailsCast che stai seguendo (è obsoleto). C'è un altro here:

enter image description here

Il problema si riduce alla child_index del fields_for references.

Ogni volta che si utilizza fields_for (che è ciò che si sta replicando con la funzionalità javascript di cui sopra), assegna un id a ciascun set di campi che crea. Questi ids vengono utilizzati nello params per separare i diversi attributi; sono anche assegnati a ciascun campo come HTML "id" property.

Quindi, il problema è che poiché non si aggiorna questo child_index ogni volta che si aggiunge un nuovo campo, sono tutti uguali.E poiché l'helper link_to_add_fields non aggiorna JS (IE consente di aggiungere campi con esattamente lo stesso child_index), ciò significa che ogni volta che si "rimuove" un campo, selezionerà tutti.


La correzione di questo è quello di impostare il child_index (io ti do una spiegazione qui di seguito).

Preferirei darti un nuovo codice piuttosto che scegliere la tua roba obsoleta per essere sincero.

ho scritto su questo qui (anche se potrebbe essere lucidato un po '): Rails accepts_nested_attributes_for with f.fields_for and AJAX

Ci sono gemme che fanno questo per voi - soluzione one chiamato Cocoon è molto popolare, anche se non un "plug and play" molti pensano che sia.

Tuttavia, è meglio sapere che tutte le opere, anche se si sceglie di usare qualcosa come Cocoon ...


fields_for

Per capire la soluzione, si deve ricordare che Rails crea forme HTML.

Lo sai probabilmente; molti no.

E 'importante perché quando ti rendi conto che HTML forme devono rispettare tutti i vincoli imposti dalla HTML, capirai che Rails non è il mago un sacco di gente sembra pensare.

Il modo per creare un modulo "nidificato" (senza aggiungere/rimuovere) la funzionalità è la seguente:

#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 

Qualcosa di importante da notare è che il vostro accepts_nested_attributes_for dovrebbe essere sul modello genitore. Cioè, il modello si sta passando dati (non i dati quello che riceve):

attributi nidificate consentono di salvare attributi record associati attraverso il genitore

#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 

Con questi oggetti costruito, siete in grado di utilizzarli nel modulo:

#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 %> 

Questo è uno STA modulo ndard che invierà i dati al controller e quindi al modello. Il metodo accepts_nested_attributes_for nel modello passerà gli attributi nidificati al modello dipendente.

-

La cosa migliore da fare con questo è quello di prendere atto del id per i campi nidificati il ​​codice di cui sopra crea. Non ho alcun esempio a portata di mano; dovrebbe mostrare che i campi nidificati hanno nomi come teachers_attributes[0][name] ecc.

La cosa importante da notare è lo [0] - questo è child_index che svolge un ruolo cruciale nelle funzionalità necessarie.


dinamica

Ora per la forma dinamica.

La prima parte è relativamente semplice ... rimozione un campo è un caso di eliminarlo dal DOM. Possiamo usare la child_index per questo, quindi abbiamo prima bisogno di sapere come impostare l'indice bambino 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 

Ora, per i punti di vista (nota si deve dividere il modulo in parziali):

#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 %> 

Questo dovrebbe rendere le varie forme ecc per voi, tra cui la fields_for. Nota lo child_index: Time.now.to_i - questo imposta un ID univoco per ogni fields_for, permettendoci di distinguere tra ogni campo di cui hai bisogno.

Fare questo dinamica poi si riduce a JS:

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

Usando questo percorso ci permette di inviare una richiesta Ajax (per ottenere un nuovo campo):

#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() 
+1

Would Faccio la stessa cosa per Class come ho fatto per l'insegnante? Ho una versione in qualche modo funzionante anche se ho bisogno di avere un child_index coinvolto da qualche parte perché penso che sia solo assegnando un token di autenticità piuttosto che un id. – BB123

+0

Quindi ho seguito [questo esempio] (https://hackhands.com/building-has_many-model-relationship-form-cocoon/) utilizzando la [Cocoon Gem] (https://github.com/nathanvda/cocoon) e tutto sembra funzionare bene, tranne quando clicco sul link "Aggiungi", aggiunge due campi piuttosto che uno solo. Penso che potrebbe essere qualcosa in corso con il mio controller ma non ne sono sicuro. Inoltre, non utilizzo alcun metodo JS o Helper. La mia preoccupazione principale è che i campi non mi diano un child_index. Piuttosto mi sta ancora dando quello che penso sia un token di autenticità (un mucchio di numeri casuali). @richpeck – BB123

+0

Sarà probabilmente un problema con i turbolinks. Prova ad aggiornare la pagina e a farlo di nuovo, se continua a inviare due richieste, dovremo capire i tuoi binding JS –

-1
add this in your js.coffe file 
$(document).on 'click', 'form .remove_', (event) -> 
$(this).prev('input[type=hidden]').val('1') 
$(this).closest('fieldset').hide() 
event.preventDefault() 

$(document).on 'click', 'form .add_teacher', (event) -> 
event.preventDefault() 
time = new Date().getTime() 
regexp = new RegExp($(this).data('id'), 'g') 
$(this).before($(this).data('fields').replace(regexp, time)) 
+0

È necessario spiegare come implementare la soluzione e principalmente la logica da seguire per risolvere il problema. Non solo copia e incolla il codice. https://github.com/ryanb/railscasts-episodes/blob/master/episode-196/revised/questionnaire-after/app/assets/javascripts/surveys.js.coffee –

Problemi correlati