提交表单并通过Ajax刷新fields_for部分 - 未定义的局部变量f作为响应

时间:2015-02-19 11:30:36

标签: javascript ruby-on-rails ajax forms nested-forms

有许多资源处理Rails表单和简单的ajax crud接口,如那些着名的Post - >评论示例。但是,我的问题有点复杂,我似乎无法让它发挥作用。

我有一个订单表单,可以包含几个line_items。在ryan bates借助nested_form gem提交任何内容之前,添加和删除了这些line_items。这一切都很好,它完美无瑕。

现在我想添加一些动态行为。当某些line_item输入字段更改总计时,应更新其他计算。为该line_item选择其他产品时,应在提交表单之前更新并显示其价格。类似的东西。我的第一种方法是在JavaScript中进行这些计算,在工作的同时,我很快意识到我将我在应用程序模型中定义的所有逻辑加倍。另外,由于您无法访问实例变量,因此在JS中执行此操作非常麻烦。

经过一番思考后,我想在有人更改某些输入值后立即提交并刷新这些fields_for line_item partials。这应该是基本的工作流程:

用户输入 - >提交订单(如果可能,只提交line_item) - >返回更新的line_item部分,刷新计算等。

将来我想将此行为扩展到不再需要保存按钮的位置。

但是我不想重新渲染整个表单(对于快速用户输入来说太慢),只是当前的fields_for partial。

问题是每当我限制我的js响应重新渲染当前fields_for partial时,form_for @order do |f|所需的f变量就会丢失。如果我在我的回复中替换整个表单,它工作正常。

这是当前的代码:

/views/orders/_form.html.slim

= nested_form_for @order do |f|
  ...
  #line-items
    = f.fields_for :line_items do |line_item|
      = render "orders/line_item_fields", f: line_item
  ...

视图/命令/ _line_item_fields.html.slim

...

= f.label :product_id, "Product"
= f.collection_select(:product_id, Product.order(:name), :id, :name, class: "order_line_items_product_id submittable" )

= f.label :quantity
= f.text_field :quantity, class: "submittable"

= f.label :price
= f.text_field :price, class: "submittable"

...

.totals
  p
    span = f.object.total_price

/app/assets/javascripts/orders.js.erb

$('body').on('change', '.submittable', function () {
  var current_item = $(this).closest('section')
  var order_id = $('form.edit_order').data('order-id')
  var data = $('form.edit_order').serialize();

  current_item.addClass('item-to-refresh')

  $.ajax({
    url: "/orders/" + order_id,
    data: data,
    dataType: "script",
    type: "patch",
    error: function(response) {
      response
    },
    success: function(response){
      response
    }
  });
}

/app/controllers/orders_controller.rb

...

def update
  @order = Order.find(params[:id])
  authorize @order

  if @order.update_attributes(order_params)
    respond_to do |format|
      format.html do
        redirect_to order_path
        flash[:success] = "Order #{@order.id} saved"
      end
      format.js
    end
  else
    render 'edit'
  end

end

...

/app/views/orders/update.js.erb

$('.item-to-refresh').replaceWith("<%= j render 'orders/line_item_fields' %>")

如上所述,响应总是伴随错误ActionView::Template::Error (undefined local variable or method 'f' for #class... - 我知道它需要f变量,但我该如何传递它?我尝试将主题中的f: ff: line_item和类似变体添加到 update.js.erb 的渲染功能,但没有成功。当然,如果我不尝试在响应中呈现表单元素而只是简单的<p>It works!</p>

,整个过程可以完美地运行

此外,我在脑海中想到的一般概念是处理网络应用中交互性的好方法吗?这仍然是一个老式的基于文档/页面的应用程序,其中重新加载页面就好了。但对于应用程序的某些部分,用户只希望得到即时反馈和结果 - 点击“保存”以获得一些计算和总计这些天是不够的。 另一方面(除非上面的代码没有告诉你)我讨厌Javascript,并希望在解决这些问题时尽量减少它。所以,我想找到一种可重用的方法来提交和刷新有关变更事件的部分内容。

2 个答案:

答案 0 :(得分:0)

试试这个

= render "orders/line_item_fields", :locals => {f: line_item}

= render partial: "orders/line_item_fields", :locals => {f: line_item}

答案 1 :(得分:0)

我知道已经有点晚了,但是我有一个同样的问题,我想在更新记录而不呈现完整的表单之后重新呈现fields_for表单。

我的解决方案是在更新(并在数据库中创建记录)后,更改由javascript动态创建的具有正确ID和名称的每个输入(选择,复选框等)的ID和名称属性。最后,动态字段看起来像是由表单构建器为编辑字段创建的字段。

除了更改id和name属性外,在fields_for表单元素之后还需要一个额外的输入type = hiddened字段。这还需要正确的ID和名称语法,例如表单中的字段,并且value属性必须是新的记录ID。

希望这会有所帮助!