2010-04-20 18 views
24

Todos hemos visto el brillante complex forms railscast donde Ryan Bates explica cómo agregar o eliminar dinámicamente objetos anidados dentro del formulario de objetos padres utilizando Javascript.Cómo agregar y eliminar campos de modelos anidados dinámicamente con Haml y Formtastic

¿Alguien tiene alguna idea sobre cómo estos métodos deben modificarse para poder trabajar con Haml Formtastic?

Para añadir un poco de contexto aquí es una versión simplificada del problema que estoy enfrentando actualmente:

forma # Maestro (que ha anidado formas de materia) [desde mi aplicación]

- semantic_form_for(@teacher) do |form| 
    - form.inputs do 
    = form.input :first_name 
    = form.input :surname 
    = form.input :city 
    = render 'subject_fields', :form => form 
    = link_to_add_fields "Add Subject", form, :subjects 

# individuo sujeto formar parcial [desde mi aplicación]

- form.fields_for :subjects do |ff| 
    #subject_field 
    = ff.input :name 
    = ff.input :exam 
    = ff.input :level 
    = ff.hidden_field :_destroy 
    = link_to_remove_fields "Remove Subject", ff 

# aplicación auxiliar (directamente desde Railscasts)

def link_to_remove_fields(name, f) 
    f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)") 
    end 

    def link_to_add_fields(name, f, association) 
    new_object = f.object.class.reflect_on_association(association).klass.new 
    fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder| 
     render(association.to_s.singularize + "_fields", :f => builder) 
    end 
    link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)} \")")) 
    end 

# application.js (directamente desde Railscasts)

function remove_fields(link) { 
    $(link).previous("input[type=hidden]").value = "1"; 
    $(link).up(".fields").hide(); 
    } 

function add_fields(link, association, content) { 
    var new_id = new Date().getTime(); 
    var regexp = new RegExp("new_" + association, "g") 
    $(link).up().insert({ 
    before: content.replace(regexp, new_id) 
    }); 
    } 

El problema con la aplicación parece estar relacionado con los métodos JavaScript - el árbol DOM de una forma Formtastic difiere enormemente de una forma carriles regulares.

He visto esta pregunta en línea algunas veces, pero todavía no he encontrado una respuesta. ¡Ahora sabes que la ayuda será apreciada por más que por mí!

Jack

+0

Se puede publicar la salida HTML de su 'fields_for: subjects'? – nfm

Respuesta

25

Estás en el camino correcto:

... el árbol DOM de una forma Formtastic difiere enormemente de una carriles regulares formulario.

Para adaptar ejemplo de Ryan como Formtastic, es útil recordar que el ayudante semantic_fields_for es similar a la semantic_form_for ayudante, que da salida a las entradas en forma de lista.

Para mantener las cosas lo más cerca posible del código Railscast como sea posible, usted tendrá que:

  • encerrar el conjunto de campos anidados en una envoltura
  • (utilizo un div con el ID subjects CSS).
  • encierran los campos anidados en un envoltorio ul/ol (he aplicado una clase nested-fields CSS.)

Esto es lo que los archivos deben tener gusto.

forma Maestros (con campos Asunto anidados):

- semantic_form_for(@teacher) do |form| 
    - form.inputs do 
    = form.input :first_name 
    = form.input :surname 
    = form.input :city 

    %h2 Subjects 
    #subjects 
     - form.semantic_fields_for :subjects do |builder| 
     = render :partial => "subject_fields", :locals => { :f => builder } 
     .links 
     = link_to_add_fields "Add Subject", form, :subjects 

campos Asunto parcial (por Asunto anidada):

%ul.nested-fields 
    = f.input :name 
    = f.input :exam 
    = f.input :level 
    = link_to_remove_fields "Remove Subject", f 

ApplicationHelper:

def link_to_remove_fields(name, f) 
    f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)") 
end 

def link_to_add_fields(name, f, association) 
    new_object = f.object.class.reflect_on_association(association).klass.new 
    fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder| 
    render(association.to_s.singularize + "_fields", :f => builder) 
    end 
    link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")")) 
end 

application.js:

function remove_fields(link) { 
    $(link).previous("input[type=hidden]").value = "1"; 
    $(link).up(".nested-fields").hide(); 
} 

function add_fields(link, association, content) { 
    var new_id = new Date().getTime(); 
    var regexp = new RegExp("new_" + association, "g") 
    $(link).up().insert({ 
    before: content.replace(regexp, new_id) 
    }); 
} 

Usando siguientes gemas:

  • Formtastic (0.9.10)
  • haml (3.0.2)
  • pepinillo (1.0.26)
  • carriles (2.3.5)
+0

Gracias por su solución, por lo que los campos de asunto se muestran como los botones, pero me desplazo a la parte superior de la pantalla sin hacer nada cuando hago clic en cualquiera de los dos (tengo js por defecto en caso de que se lo pregunte). Probé la solución de Steve y con eso el enlace de eliminación funciona, pero desafortunadamente no es el botón agregar. Puede ser que mi marcado sea un poco diferente, usando Rails 3 y el último Formtastic. ¿Algunas ideas? –

+0

Solo he probado esta configuración con las gemas que he enumerado anteriormente. – Bennett

+0

Además, no parece que siga mi ejemplo exactamente, por lo que podría tener su formulario estructurado de manera diferente. Recomiendo que obtenga el link_to_add_fields y add_fields helper trabajando primero antes de continuar con la eliminación de campos. – Bennett

3

application.js para jQuery:

function remove_fields(link) { 
    $(link).prev("input[type=hidden]").val("1"); 
    $(link).parent(".nested-fields").hide(); 
} 

function add_fields(link, association, content) { 
    var new_id = new Date().getTime(); 
    var regexp = new RegExp("new_" + association, "g") 
    $(link).parent().before(content.replace(regexp, new_id)); 
} 
+0

Gracias - con esto, el botón de eliminación funcionó bien, pero no el botón Agregar .... usando Rails3 y la última versión de Formtastic. –

1

En Rails 3, no es necesario utilizar la función h() en el helper. Simplemente omita y debería funcionar.

1

He estado golpeando mi cabeza contra esto una y otra vez durante años, y hoy he descubierto algo de lo que estoy orgulloso - elegante, discreto, y puedes hacerlo solo dos o tres (muy largo) líneas de código.

ParentFoo has_many NestedBars, con accept_nested_parameters apropiados y todas esas cosas. Puede representar un conjunto de campos para una barra anidada en un atributo de anidación de datos agregados en el enlace, usando: index_inicio para establecer una cadena que puede reemplazar más adelante. Se pasa esa cadena en un segundo parámetro, los datos-add-anidada reemplazar

parent_foos/_form.html.haml

- semantic_form_for @parent_foo do |f| 
    -# render existing records 
    - f.semantic_fields_for :nested_bars do |i| 
    = render 'nested_bar_fields', :f => i 

    # magic! 
    - reuse_fields = f.semantic_fields_for :nested_bars, @parent_foo.nested_bars.build, :child_index => 'new_nested_bar_fields' do |n| render('nested_bar_fields', :f => n) end 
    = link_to 'Add nested bar', '#', 'data-add-nested' => reuse_fields, 'data-add-nested-replace' => 'new_nested_bar_fields' 

los campos anidados parciales es muy sofisticado

parent_foos/_nested_bar_fields.html .haml

- f.inputs do 
    = f.input :name 
    = f.input :_delete, :as => :boolean 

En su jQuery que ates el clic de elementos con un complemento anidada de datos de campo (que he usado en directo() aquí, así que las formas-ajax cargado trabajarán) para insertar los campos i nto al DOM, reemplazando la cadena con una nueva ID. Aquí estoy haciendo lo simple de insertar los nuevos campos antes del enlace(), pero también podría proporcionar un atributo add-anidado-reemplazar-objetivo en el enlace que indica en qué lugar del DOM desea que terminen los nuevos campos.

application.js

$('a[data-add-nested]').live('click', function(){ 
    var regexp = new RegExp($(this).data('add-nested-replace'), "g") 
    $($(this).data('add-nested').replace(regexp, (new Date).getTime())).insertBefore($(this)) 
}) 

Se puede poner esto en un ayudante, por supuesto; Lo presento aquí directamente en la vista, por lo que el principio es claro, sin perder el tiempo en la metaprogramación. Si le preocupan los nombres que colisionan, puede generar una ID única en ese helper y pasarla en anidados y reemplazar.

obtener fantasía con el comportamiento de eliminación se deja como un ejercicio para el lector.

(mostrado por los carriles 2, ya que es el sitio que estoy trabajando hoy - casi el mismo en Rails 3)

+0

nota: puede que no sea tan fácil en Rails 3, gracias a html escapar :( –

+0

usando escape_once parece funcionar, como en este ejemplo semi-relacionados con la inserción de un solo campo: - new_field = escape_once f.input (: related_thing) = link_to 'Agregar otro', '#', 'data-insert' => new_field –

+1

He desarrollado esta técnica un poco más para usar sin un modelo - tuve un caso en el que un cliente quería especificar conjuntos de campos para mostrar en un informe. Esta versión usa un bloque en lugar de una plantilla separada para los campos, y usa un 'modelo genérico' para generar el parámetro reemplazable. Aquí está la esencia: https://gist.github.com/1554795 –

Cuestiones relacionadas