简体   繁体   English

如何使用Haml和Formtastic动态添加和删除嵌套模型字段

[英]How to add and remove nested model fields dynamically using Haml and Formtastic

We've all seen the brilliant complex forms railscast where Ryan Bates explains how to dynamically add or remove nested objects within the parent object form using Javascript. 我们都看到了辉煌的复杂形式railscast ,其中Ryan Bates解释了如何使用Javascript在父对象表单中动态添加或删除嵌套对象。

Has anyone got any ideas about how these methods need to be modified so as to work with Haml Formtastic? 有没有人对如何修改这些方法以便与Haml Formtastic合作有任何想法?

To add some context here's a simplified version of the problem I'm currently facing: 在这里添加一些上下文是我目前面临的问题的简化版本:

# Teacher form (which has nested subject forms) [from my application] #教师表格(有嵌套的主题表格)[来自我的申请]

- 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   

# Individual Subject form partial [from my application] #个人主题形式部分[来自我的申请]

- 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 

# Application Helper (straight from Railscasts) #Application Helper(直接来自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 (straight from Railscasts) #Application.js(直接来自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)
  });
  }

The problem with implementation seems to be with the javascript methods - the DOM tree of a Formtastic form differs greatly from a regular rails form. 实现的问题似乎与javascript方法有关 - Formtastic形式的DOM树与常规rails形式有很大不同。

I've seen this question asked online a few times but haven't come across an answer yet - now you know that help will be appreciated by more than just me! 我已经在网上看过几次这个问题,但还没有找到答案 - 现在你知道帮助将不仅仅是我的赞赏!

Jack 插口

You're on the right track: 你走在正确的轨道上:

...the DOM tree of a Formtastic form differs greatly from a regular rails form. ... Formtastic形式的DOM树与常规轨道形式有很大不同。

To adapt Ryan's example for formtastic, it's helpful to be reminded that the semantic_fields_for helper is similar to the semantic_form_for helper, which outputs the inputs in list form. 为了使Ryan的示例适用于formtastic,有必要提醒一下, semantic_fields_for帮助器类似于semantic_form_for帮助器,它以列表形式输出输入。

To keep things as close to the Railscast code as possible, you'll need to: 为了使事情尽可能接近Railscast代码,您需要:

  • enclose the collection of nested fields in a wrapper (I use a div with the subjects CSS ID.) 在包装器中包含嵌套字段的集合(我使用带有subjects CSS ID的div。)
  • enclose the nested fields in a ul/ol wrapper (I applied a nested-fields CSS class.) 将嵌套字段包含在ul / ol包装器中(我应用了nested-fields CSS类。)

Here's what your files should like. 这是你的文件应该是什么样的。

Teacher form (with nested Subject fields): 教师表单(具有嵌套的主题字段):

- 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

Subject fields partial (for nested Subject): 主题字段部分(对于嵌套主题):

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

ApplicationHelper: 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: 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)
  });
}

Using following gems: 使用以下宝石:

  • formtastic (0.9.10) formtastic(0.9.10)
  • haml (3.0.2) haml(3.0.2)
  • gherkin (1.0.26) 小黄瓜(1.0.26)
  • rails (2.3.5) 铁轨(2.3.5)

application.js for jQuery: 适用于jQuery的application.js:

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));
}

In Rails 3, there's no need to use the h() function in the helper. 在Rails 3中,不需要在帮助器中使用h()函数。 Just omit and it should work. 只是省略,它应该工作。

I've been beating my head against this one on and off for years, and I just today came up with something I'm proud of - elegant, unobtrusive, and you can do it only two or three (really long) lines of code. 我多年来一直在打击和关闭这个,我今天想出了一些我很自豪的东西 - 优雅,不引人注目,你只能做两到三个(很长的)代码行。

ParentFoo has_many NestedBars, with appropriate accepts_nested_parameters and all those goodies. ParentFoo has_many NestedBars,带有相应的accepts_nested_pa​​rameters和所有这些好东西。 You render a set of fields for a nested_bar into a data-add-nested attribute on the link, using the :child_index to set a string that you can replace later. 您可以使用:child_index设置一个可以在以后替换的字符串,将nested_bar的一组字段渲染到链接上的data-add-nested属性中。 You pass that string in a second parameter, data-add-nested-replace 您在第二个参数data-add-nested-replace中传递该字符串

parent_foos/_form.html.haml 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'

the nested fields partial is nothing fancy 嵌套的字段部分没什么特别的

parent_foos/_nested_bar_fields.html.haml parent_foos / _nested_bar_fields.html.haml

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

In your jQuery you bind the click of elements with a data-add-nested field (I've used live() here so ajax-loaded forms will work) to insert the fields into the DOM, replacing the string with a new ID. 在您的jQuery中,您将元素的点击与data-add-nested字段绑定(我在这里使用了live(),因此ajax加载的表单将起作用)将字段插入DOM,用新ID替换字符串。 Here I'm doing the simple thing of inserting the new fields before() the link, but you could also provide an add-nested-replace-target attribute on the link saying where in the DOM you want the new fields to wind up. 在这里我做了一个简单的事情就是在链接之前插入新字段,但你也可以在链接上提供一个add-nested-replace-target属性,说明DOM中你希望新字段在哪里结束。

application.js 的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))
})

You could put this in a helper, of course; 当然,你可以把它放在帮助器里; I'm presenting it here directly in the view so the principle is clear without mucking about in metaprogramming. 我在这里直接在视图中呈现它,所以原理很清楚,而不会在元编程中徘徊。 If you're worried about names colliding, you could generate a unique ID in that helper and pass it in nested-replace. 如果您担心名称冲突,可以在该帮助程序中生成唯一ID并将其传递给嵌套替换。

getting fancy with the delete behaviour is left as an exercise for the reader. 对删除行为持幻想是留给读者的练习。

(shown for Rails 2 because that's the site I'm working on today - almost the same in Rails 3) (显示为Rails 2,因为那是我今天正在处理的网站 - 在Rails 3中几乎相同)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM