Dado que nadie ha ofrecido una respuesta a esto, incluso después de una recompensa, finalmente he logrado hacerlo funcionar yo mismo. ¡Esto no se suponía que fuera un stumper! Esperemos que esto sea más fácil de hacer en Rails 3.0.
El ejemplo de Andy es una buena manera de eliminar registros directamente, sin enviar un formulario al servidor. En este caso particular, lo que realmente estoy buscando es una forma de agregar/eliminar campos dinámicamente antes de hacer una actualización a un formulario anidado. Este es un caso ligeramente diferente, porque a medida que se eliminan los campos, en realidad no se eliminan hasta que se envía el formulario. Probablemente terminaré usando ambos dependiendo de la situación.
He basado mi implementación en Tim Riley's complex-forms-examples fork en github.
Primero creó los modelos, y asegurarse de que son compatibles atributos anidados:
class Person < ActiveRecord::Base
has_many :phone_numbers, :dependent => :destroy
accepts_nested_attributes_for :phone_numbers, :reject_if => lambda { |p| p.values.all?(&:blank?) }, :allow_destroy => true
end
class PhoneNumber < ActiveRecord::Base
belongs_to :person
end
crear una vista parcial para campos de formulario del número de teléfono:
<div class="fields">
<%= f.text_field :description %>
<%= f.text_field :number %>
</div>
Siguiente escribir una vista básica de edición para el Modelo de persona:
<% form_for @person, :builder => LabeledFormBuilder do |f| -%>
<%= f.text_field :name %>
<%= f.text_field :email %>
<% f.fields_for :phone_numbers do |ph| -%>
<%= render :partial => 'phone_number', :locals => { :f => ph } %>
<% end -%>
<%= f.submit "Save" %>
<% end -%>
Esto funcionará mediante la creación de un conjunto de campos de plantilla para el modelo PhoneNumber que podemos duplicar con javascript. Vamos a crear métodos de ayuda en app/helpers/application_helper.rb
para esto:
def new_child_fields_template(form_builder, association, options = {})
options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new
options[:partial] ||= association.to_s.singularize
options[:form_builder_local] ||= :f
content_tag(:div, :id => "#{association}_fields_template", :style => "display: none") do
form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f|
render(:partial => options[:partial], :locals => { options[:form_builder_local] => f })
end
end
end
def add_child_link(name, association)
link_to(name, "javascript:void(0)", :class => "add_child", :"data-association" => association)
end
def remove_child_link(name, f)
f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", :class => "remove_child")
end
Ahora añadir estos métodos de ayuda a la edición parcial:
<% form_for @person, :builder => LabeledFormBuilder do |f| -%>
<%= f.text_field :name %>
<%= f.text_field :email %>
<% f.fields_for :phone_numbers do |ph| -%>
<%= render :partial => 'phone_number', :locals => { :f => ph } %>
<% end -%>
<p><%= add_child_link "New Phone Number", :phone_numbers %></p>
<%= new_child_fields_template f, :phone_numbers %>
<%= f.submit "Save" %>
<% end -%>
ahora tiene la plantilla JS hecho. Enviará una plantilla en blanco para cada asociación, pero la cláusula :reject_if
en el modelo los descartará, dejando solo los campos creados por el usuario. Actualización:He repensado este diseño, consulte a continuación.
Esto no es realmente AJAX, ya que no hay ninguna comunicación de pasar al servidor más allá de la carga de la página y el formulario de envío, pero sinceramente no podría encontrar una manera de hacerlo después del hecho.
De hecho, esto puede proporcionar una mejor experiencia de usuario que AJAX, ya que no tiene que esperar una respuesta del servidor para cada campo adicional hasta que haya terminado.
Finalmente tenemos que conectar esto con javascript. Añadir lo siguiente a su fichero `/ javascript/application.js públicos:
$(function() {
$('form a.add_child').click(function() {
var association = $(this).attr('data-association');
var template = $('#' + association + '_fields_template').html();
var regexp = new RegExp('new_' + association, 'g');
var new_id = new Date().getTime();
$(this).parent().before(template.replace(regexp, new_id));
return false;
});
$('form a.remove_child').live('click', function() {
var hidden_field = $(this).prev('input[type=hidden]')[0];
if(hidden_field) {
hidden_field.value = '1';
}
$(this).parents('.fields').hide();
return false;
});
});
En este momento usted debe tener una forma dinámica barebones! El javascript aquí es realmente simple, y podría hacerse fácilmente con otros marcos. Puede reemplazar fácilmente mi código application.js
con prototipo + lowpro, por ejemplo. La idea básica es que no está incorporando funciones javascript gigantescas en su marcado, y no tiene que escribir tediosas funciones phone_numbers=()
en sus modelos. Todo funciona. ¡Hurra!
Después de algunas pruebas más, he concluido que las plantillas necesitan ser movido fuera de los campos <form>
. Mantenerlos allí significa que se envían de vuelta al servidor con el resto del formulario, y eso simplemente genera dolores de cabeza más adelante.
He añadido esto a la parte inferior de mi diseño:
<div id="jstemplates">
<%= yield :jstemplates %>
</div
y modificó el new_child_fields_template
ayudante:
def new_child_fields_template(form_builder, association, options = {})
options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new
options[:partial] ||= association.to_s.singularize
options[:form_builder_local] ||= :f
content_for :jstemplates do
content_tag(:div, :id => "#{association}_fields_template", :style => "display: none") do
form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f|
render(:partial => options[:partial], :locals => { options[:form_builder_local] => f })
end
end
end
end
Ahora puede retirar las cláusulas :reject_if
a partir de modelos y dejar de preocuparse las plantillas que se envían de vuelta.
¿Tiene problemas para escribir jQuery ajaxified no intrusivo en general o es específico para formularios anidados? Si quieres ser discreto, deshacerse de jRails es un buen comienzo. –
Necesito poder admitir formularios anidados, pero estoy tratando de comprender lo básico en este momento. Tengo jRails instalado, pero no lo estoy usando; como dije, quiero hacerlo de forma discreta, y preferiría no tener que aprender otra DSL cuando soy perfectamente bueno en javascript. –