如何在 Rails 上启用 Turbolinks 的情况下通过 AJAX 正确渲染部分内容?

How to properly render a partial via AJAX with Turbolinks enabled on Rails?

我目前正在开发一个 Rails 应用程序,该应用程序必须与外部 Web API 通信,根据结果更新数据库记录并异步更新视图。

我目前在做什么

我有一个 list 操作,我在其中显示直接来自数据库的数据,其中包含 table 中发送的带有分页的电子邮件列表。呈现视图后,我需要向应用程序发出 AJAX 请求(在不同的路由上),该请求向外部 Web API 发出请求,更新数据库,然后使用 table 包含所有电子邮件,与原始 list 操作中呈现的相同。

以下代码处理 AJAX 请求:

# _mail_list_loader.js.erb
document.addEventListener("turbolinks:load", function() {
  $.ajax({
    url: '/certified_mail/mail_list',
    type: 'get',
    data: {
      page: <%= @page %>,
      rut: <%= @rut || 'null' %>,
      from: <%= @from || 'null' %>,
      to: <%= @to || 'null' %>,
      ids: <%= @outdated_message_ids.to_json.html_safe %>
    }
  });
});

哪个响应是 .js.erb 呈现部分实际 table:

# mail_list.js.erb
$('#mail-list').html('<%= j render 'certified_mail/mail_list' %>');

_mail_list_loader.js.erb 在我的 application.html.erb 中呈现:

# application.html.erb
<head>
    ...
    <script><%= render 'certified_mail/mail_list_loader.js' %></script>
</head>

对于首先加载 table 的操作和处理 AJAX 请求的操作,我的控制器看起来像这样:

class CertifiedMailController < ApplicationController
  def list
    if params[:page].nil? || params[:page].to_i <= 0
      redirect_to action: :list, params: {
        page: 1,
        from: params[:from].to_s.empty? ? nil : params[:from],
        to: params[:to].to_s.empty? ? nil : params[:to]
      }
    else
      # Handle filters and get emails from the DB
    end
  end

  def mail_list
    if request.xhr?
      # Make API request, update accordingly,
      # handle filters and get emails from the DB
      respond_to do |format|
        format.js
      end
    else
      redirect_to action: :list
    end
  end
end

我的问题是什么

当页面第一次加载时,它从控制器加载,并且 AJAX 请求被正确发送,但是如果我转到下一页(发送带有不同 GET 参数的请求 page),页面直接从controller加载,但是发送了两个AJAX请求,一个是第一页,一个是第二页,如下图,HTML为table 有点随机变化,并没有真正在 table:

中显示正确的数据

我曾经有 JavaScript 使 AJAX 请求定义在 body 中,而不是 header 中,但这样请求就会不断重复每次页面更改,每次都有越来越多的请求。我做了一些搜索,发现可以通过将代码移动到 head.

来解决额外调用的问题

我真的不知道我的问题是否与 Turbolinks 处理请求的方式严格相关,但我需要的是让应用程序呈现完整视图,然后发送 AJAX 请求将相同的 GET 参数传递给不同的操作,该操作仅使用 table.

呈现局部

非常感谢任何帮助!

根据我在 中读到的内容,在某些链接上禁用 Turbolinks 正是我需要的,以便完全重新加载我的 JavaScript,所以我转到了发送请求更改的分页链接参数(例如 page)并简单地向其添加 'data-turbolinks': false

现在没有额外的 AJAX 调用,因为链接现在只呈现整个 HTML,而不是通过 Turbolinks。

因为您在 _mail_list_loader.js.erb 中有动态内容,您可能会注意到 <head> 中的脚本标记在随后的页面加载中被复制。虽然它是作为一个代码块编写的,但 Turbolinks 并没有那样 "see",而是在每次加载时附加一个新的脚本元素。

我建议您尝试将 JavaScript 移动到 .js 文件中,并将它们包含在您的应用程序清单中。您可以通过将它们呈现为属性并让 JS 拾取它们来获取动态服务器生成的变量。

例如,您可能想尝试以下操作:

# app/views/certified_mail/list.html.erb
<% props = { page: @page, rut: @rut, from: @from, to: @to, ids: @outdated_message_ids } %>
<table data-component="mail-list" data-props="<%= props.to_json %>" id="mail_list_<%= @page %>">
 …
</table>

# app/assets/javascripts/load_mail_list.js
;(function () {
  function loadMailList (data, callback) {
    $.ajax({
      url: '/certified_mail/mail_list',
      type: 'get',
      dataType: 'html',
      data: data,
      success: callback
    })
  }

  $(document).on('turbolinks:load', function () {
    var $element = $('[data-component=mail-list]')
    loadMailList($element.data('props'), function (html) {
      $element.html(html)
    })
  })
})()

# app/controllers/certified_mail_controller.rb
def mail_list
  if request.xhr?
    # Make API request, update accordingly,
    # handle filters and get emails from the DB
    render 'certified_mail/mail_list'
  else
    redirect_to action: :list
  end
end

回调应确保响应将列表呈现到正确的元素中(而以前,如果有人快速分页,则可能存在第 2 页的列表呈现在第 3 页的风险)。

希望对您有所帮助。