代码之家  ›  专栏  ›  技术社区  ›  Jack Kinsella

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

  •  24
  • Jack Kinsella  · 技术社区  · 14 年前

    我们都看过精彩的表演 complex forms railscast 其中Ryan Bates解释了如何使用Javascript在父对象窗体中动态添加或删除嵌套对象。

    有没有人知道如何修改这些方法以便与Haml Formtastic一起工作?

    为了增加一些背景知识,下面是我目前面临的问题的简化版本:

    - 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   
    

    #个别科目表格部分[来自我的申请]

    - 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 
    

    #应用程序助手(直接来自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(直接来自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)
      });
      }
    

    实现的问题似乎在于javascript方法——Formtastic表单的DOM树与常规rails表单有很大的不同。

    杰克

    4 回复  |  直到 14 年前
        1
  •  27
  •   Bennett    14 年前

    你在正确的轨道上:

    与普通钢轨有很大区别 形式。

    为了将Ryan的例子改编成formtastic,提醒我们 semantic_fields_for 助手类似于 semantic_form_for helper,以列表形式输出输入。

    为了尽可能接近Railscast代码,您需要:

    • 将嵌套字段的集合封装在包装器中(我使用带有 subjects
    • 将嵌套字段括在ul/ol包装器中(我应用了 nested-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
    

    部分主题字段(对于嵌套主题):

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

    应用程序帮助程序:

    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
    

    应用程序.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)
      });
    }
    

    使用以下宝石:

    • formtastic(0.9.10)
    • 小黄瓜(1.0.26)
    • 轨道(2.3.5)
        2
  •  3
  •   Steve Oscar    14 年前

    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));
    }
    
        3
  •  1
  •   Nolsto    14 年前

    在Rails3中,不需要在helper中使用h()函数。只要省略就行了。

        4
  •  1
  •   stephan.com    14 年前

    我已经断断续续地用我的头来对付这个了好几年了,今天我想到了一些我引以为豪的东西——优雅,不引人注目,你只能做两三行(非常长的)代码。

    父\u foos/\u 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'
    

    嵌套的部分字段并不奇怪

    父项\u foos/\u嵌套\u bar\u fields.html.haml

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

    在jQuery中,您使用data add嵌套字段绑定元素的点击(我在这里使用了live(),这样ajax加载的表单就可以工作)将字段插入DOM,用新ID替换字符串,但是您也可以在链接上提供add-nested-replace-target属性,说明您希望新字段在DOM中的什么位置结束。

    $('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))
    })
    

    当然,你可以把这个放在助手里;我在这里直接以视图的形式展示它,这样原则就很清楚了,而不用在元编程中乱来。如果您担心名称冲突,可以在该助手中生成一个唯一的ID,并将其传递给嵌套的replace。

    对删除行为的想象留给读者作为练习。