【问题标题】:Safari 11.1: ajax/XHR form submission fails when input[type=file] is emptySafari 11.1:当 input[type=file] 为空时,ajax/XHR 表单提交失败
【发布时间】:2018-09-11 20:04:32
【问题描述】:

更新:截至Webkit build r230963,此问题已在 Webkit 中解决。

============

自从最近 macOS 和 iOS 上的 Safari 11.1 更新以及 Safari 技术预览版 11.2 中,当 input[type=file] 字段没有选择文件时,我的 Web 应用程序中的 $.ajax 调用失败(不是必需的)以我的形式)。当字段确实选择了文件时不会失败。

ajaxerror 回调运行,Safari 控制台包含以下消息:Failed to load resource: The operation couldn’t be completed. Protocol error。我是 HTTPS 并通过 HTTPS 提交到同一域(和服务器)上的位置。

在 11.1 更新之前,$.ajax 调用在没有选择文件时提交得很好。最新版本的 Chrome 和 Firefox 没有问题。

我的代码的相关部分:

输入:

Browse... <input id="file-upload" type="file" name="image" accept=".jpg,.jpeg">

JS:

var formData = new FormData($(this)[0]);
$.ajax({
    type: 'POST',
    enctype: 'multipart/form-data',
    url: '../process.php',
    data: formData,
    contentType: false,
    processData: false,
    cache: false,
    success: function(response) { ... },
    error: function() { //my code reaches here }
});

作为一个临时(希望)解决方案,我检测到一个空文件字段,并在调用 ajax 之前将其从 formData 中删除,并且一切都按预期/之前工作:

$("input[type=file]").each(function() {
    if($(this).val() === "") {
        formData.delete($(this).attr("name"));
    }
});

我做错了什么,Safari 是否存在问题,或者 Safari 中是否存在需要在 ajax 调用中考虑的变化?

【问题讨论】:

  • 很好的临时解决方案,但如果应用于所有 Safari 浏览器,则在 safari 11 下不起作用。你找到任何“真正”的解决方案了吗?
  • @Karem - 感谢您的提醒!幸运的是,我的应用程序的用户不会低于 Safari 11,但很高兴知道这一点。不,还没有解决方案。我向 Apple 提交了一份雷达/错误报告,他们今天才要求我提供系统诊断信息。
  • 遇到了同样的问题,这篇文章对我帮助很大。谢谢你。 FWIW 我相信 FormDeta#delete() 仅在 Safari 11 或更高版本中受支持。因此,您可能必须添加版本检查。 caniuse.com/#feat=xhr2 无论如何,谢谢你的信息。
  • 是的 delete() 可能是它。有关如何使用 javascript 添加版本检查的任何建议?
  • 有为 WebKit 打开的 bugzilla 项目,而且似乎也是一个 rdar。 bugs.webkit.org/show_bug.cgi?id=184490

标签: javascript jquery ajax forms safari


【解决方案1】:

更新:旧答案在 Firefox 中不起作用。

Firefox 在空文件字段(而不是其他浏览器中的 File 对象)上为 FormData.get() 返回空字符串。因此,当使用旧的解决方法时,空的&lt;input type="file"&gt; 将像空的&lt;input type="text"&gt; 一样发送。不幸的是,在创建 FormData 对象后,没有办法区分空文件和空文本。

改用此解决方案:

var $form = $('form')
var $inputs = $('input[type="file"]:not([disabled])', $form)
$inputs.each(function(_, input) {
  if (input.files.length > 0) return
  $(input).prop('disabled', true)
})
var formData = new FormData($form[0])
$inputs.prop('disabled', false)

现场演示:https://jsfiddle.net/ypresto/05Lc45eL/

对于非jQuery环境:

var form = document.querySelector('form')
var inputs = form.querySelectorAll('input[type="file"]:not([disabled])')
inputs.forEach(function(input) {
  if (input.files.length > 0) return
  input.setAttribute('disabled', '')
})
var formData = new FormData(form)
inputs.forEach(function(input) {
  input.removeAttribute('disabled')
})

对于 Rails (rails-ujs/jQuery-ujs):https://gist.github.com/ypresto/cabce63b1f4ab57247e1f836668a00a5


旧答案:

过滤 FormData(在 Ravichandra Adiga 的回答中)更好,因为它不操纵任何 DOM。

但是the order of parts in FormData is guaranteed to be the same order to input elements in form,根据&lt;form&gt; 规范。如果有人依赖此规范,它可能会导致另一个错误。

sn-p 下面将保持 FormData 顺序和空白部分。

var formDataFilter = function(formData) {
    // Replace empty File with empty Blob.
  if (!(formData instanceof window.FormData)) return
  if (!formData.keys) return // unsupported browser
  var newFormData = new window.FormData()
  Array.from(formData.entries()).forEach(function(entry) {
    var value = entry[1]
    if (value instanceof window.File && value.name === '' && value.size === 0) {
      newFormData.append(entry[0], new window.Blob(), '')
    } else {
      newFormData.append(entry[0], value)
    }
  })
  return newFormData
}

现场示例在这里: https://jsfiddle.net/ypresto/y6v333bq/

对于 Rails,请参见此处:https://github.com/rails/rails/issues/32440#issuecomment-381185380

(注意:iOS 11.3 的 Safari 有这个问题,但 11.2 没有。)

【讨论】:

  • 谢谢@ypresto。我会调查这个的!顺便说一句,iOS 11.2 使用 Safari 11.0.3,这就是问题不存在的原因。
  • 非常感谢!我们在 iPhone 浏览器上有 nginx 错误 400,这个技巧救了我们!
【解决方案2】:

对于解决方法,我使用 jQuery remove() 方法从 DOM 中完全删除输入类型文件。

$("input[type=file]").each(function() {
    if($(this).val() === "") {
        $(this).remove();
    }
});

【讨论】:

  • 这个问题是我必须在 always() 方法中恢复字段。完成 AJAX 调用后,用户需要再次与表单交互,包括文件输入。
  • 我明白也许你应该在 Ajax 方法的回调中添加它们,这个解决方法对我有用。
  • 您可以在输入中添加一个“禁用”属性,这样就不会在表单数据中收集它;然后,当您有要上传的文件时,您可以删除该属性。
  • @MatteoSteccolini 解决方案有效,我使用它而不是删除。 $(this).prop("禁用", true);如果您需要它,只需使用 $(this).prop("disabled", false);
【解决方案3】:

截至Webkit build r230963,此问题已在 Webkit 中得到解决。我下载并运行了该版本并确认问题已解决。不知道何时会公开发布包含此修复程序的 Safari。

【讨论】:

    【解决方案4】:

    我在 Perl 程序中处理了似乎相同的问题

    Processing multipart/form-data in Perl invokes Apache-error with Apple-devices, when a form-file-element is empty

    解决方法是在分配表单数据之前删除表单元素:

    $('#myForm').find("input[type='file']").each(function(){
        if ($(this).get(0).files.length === 0) {$(this).remove();}
    });
    var fData = new FormData($('#myForm')[0]);
    ...
    

    【讨论】:

    • 这完全是真的,你救了我的命!如果您使用 ajax 上传文件,这是 safari 必须做的事情
    【解决方案5】:
        var fileNames = formData.getAll("filename[]");
        formData.delete("filename[]");
        jQuery.each(fileNames, function (key, fileNameObject) {
            if (fileNameObject.name) {
                formData.append("filename[]", fileNameObject);
            }
        });
    

    试试这个!!

    【讨论】:

    • 我的临时解决方案暂时有效,直到此问题得到解决。
    【解决方案6】:

    这对我来说可以检查输入字段是否为空。如果为空,则在创建 FormData 之前禁用输入字段。创建 FormData 后,删除“禁用”属性。与其他答案的不同之处在于,我搜索“input[0].files.length == 0”。

    // get the input field with type="file"
    var input = $('#myForm').find("input[type='file']")
    
    // add the "disabled" attribute to the input
    if (input[0].files.length == 0) {
      input.prop('disabled', true);
    }
    
    // create the formdata  
    var formData = new FormData($(this)[0]);
    
    // remove the "disabled" attribute
    input.prop('disabled', false);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-06
      • 2015-05-08
      • 1970-01-01
      • 2012-08-07
      • 2011-09-06
      • 2012-06-10
      • 2014-09-12
      • 2016-10-17
      相关资源
      最近更新 更多