首页 > 解决方案 > 当表单中存在文件字段时,Rails 不发出 Ajax 请求

问题描述

问题总结

根据文档,使用时form_with,Rails 默认发出 Ajax 请求。我需要一个 Ajax 请求,但我发现了一种情况,改为发出标准浏览器请求。我很想知道我做错了什么以及如何解决它。

期望行为:在表单提交时发出 Ajax 请求,这样我就可以返回仅更改显示页面的一部分的 JavaScript。

当前行为:由于该路由仅适用于 XHR,因此正在发出 404 的标准浏览器请求。(如果我让所有请求都可以访问该路由,则整个页面都会被替换,这仍然是非常不可取的。)

编码

这是生成表单的代码:

<%= form_with(url: restore_path,
              method: 'post',
              multipart: true,
              id: 'restore-form') do %>

  <label for="file">Backup file:</label>
  <%= file_field_tag 'backup_file', required: true %>

  <%= submit_tag('Restore',
                 id: 'restore-submit-button',
                 class: 'btn btn-primary',
                 data: { disable_with: 'Restoring...' }) %>

<% end %>

但是,这不会产生 Ajax 请求。restore_path是一条仅限 XHR 的路线...

  constraints(->(req) { req.xhr? }) do
    # other routes snipped for brevity's sake
    post 'restore' => 'backups#restore'
  end

...并且提交表单会导致 404,因为该请求是标准浏览器请求。

我在尝试调试时发现的一个奇怪现象

如果我将字段从 a 更改file_field_tag为 a text_field_tag,则请求将作为 Ajax 提交。当我改变

<%= file_field_tag 'file', required: true %>

<%= text_field_tag 'test_field', nil, required: true %>

并保持其他一切不变,按预期发出了 Ajax 请求。生成的 HTML 中的唯一变化(除了真实性令牌的确切值)是

<input type="file" name="file" id="file" required="required">

更改为

<input type="text" name="test_field" id="test_field" required="required">

......正如人们所期望的那样。但是表单提交行为完全改变了。

回购

该项目是开源的;上述代码位于https://github.com/steve-mcclellan/j-scorer/blob/master/app/views/stats/_options.html.erb

我尝试过的另一件事

我从头开始创建了一个小的新 Rails 应用程序,以查看是否可以重现该问题,但我不能。即使存在文件上传,也会发出 Ajax 请求。

停!

我的想法不多了。我究竟做错了什么?当文件字段存在时,如何让我的恢复表单发出 Ajax 请求?

标签: jqueryruby-on-railsajaxforms

解决方案


知道了。文档假设我将rails-ujs适配器用于不显眼的 JavaScript(自 Rails 5.1 开始内置)。我的应用程序是在 Rails 4 时代创建的,并使用旧的jquery-ujs适配器。

浏览器本身不会发送包含文件上传的 Ajax 请求。rails-ujs有一个内置的解决方法,但jquery-ujs没有。

从这里开始有两种方法。

长期的解决方案是切换到rails-ujs目前作为 Rails 的一部分维护的 . (在撰写本文时,jquery-ujs已经两年半没有更新了。)

不幸的是,这两个适配器有一些接口差异,因此可能需要在整个应用程序的 JavaScript 中进行一些更改。具体见这篇文章

快速修复?嗯,有一个宝石。(文档表明它适用于 Rails 3,但它对我的应用程序很有吸引力,在撰写本文时它正在运行 Rails 6.0.3.3。)

除了Gemfile

gem 'remotipart', '~> 1.4.4'

除了app/assets/javascripts/application.js(在现有jquery_ujs行之后):

//= require jquery.remotipart

它有效。该请求是通过 Ajax 发出的。它可以毫无问题地访问我的仅限 XHR 的路线,并呈现仅更新部分页面的 JavaScript 视图。

胜利!


推荐阅读