web-dev-qa-db-ja.com

Safari 11.1:input [type = file]が空の場合、ajax / XHRフォームの送信が失敗する

UPDATEWebkit build r23096 の時点で、この問題はWebkitで解決されました。

===========

MacOSとiOS、およびSafari Technology Preview 11.2での最近のSafari 11.1アップデート以降、$.ajaxフィールドにファイルが選択されていない場合(私のアプリケーションではinput[type=file]呼び出しが失敗します(そうではありません)私のフォームに必要)。フィールドdoesにファイルが選択されている場合、失敗はありません。

errorajaxコールバックが実行され、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 }
});

一時的な(うまくいけば)解決策として、空のファイルフィールドを検出し、formData呼び出しの前にajaxから削除して、すべてが期待どおり/前に動作するようにします。

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

私は何か間違っていますか、Safariに問題がありますか、またはAjaxコールで今説明する必要があるSafariに変更がありますか?

38
Matt.

Webkit build r23096 の時点で、この問題はWebkitで解決されました。そのビルドをダウンロードして実行し、問題が解決したことを確認しました。この修正を含むSafariのパブリックリリースがいつ利用可能になるかはわかりません。

5
Matt.

更新:古い回答はFirefoxで機能しません。

Firefoxは、(他のブラウザのFileオブジェクトの代わりに)空のファイルフィールドのFormData.get()に対してちょうど空の文字列を返します。したがって、古い回避策を使用すると、空の<input type="file">が空の<input type="text">のように送信されます。残念ながら、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を操作しないため、より優れています。

ただし、 FormDataのパーツの順序は、フォームの入力要素と同じ順序であることが保証されています<form>仕様に従っています。誰かがこの仕様に依存している場合、別のバグが発生する可能性があります。

以下のスニペットは、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-38118538

(注:iOS 11.3のSafariにはこの問題がありますが、11.2にはありません。)

18
ypresto

回避策として、jQueryのremove()メソッドを使用してDOMから入力タイプファイルを完全に削除します。

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

Perlプログラムで同じ問題のように見えるものに取り組みました

form-file-elementが空の場合、Perlでmultipart/form-dataを処理するとAppleデバイスでApacheエラーが呼び出されます

回避策は、formdataが割り当てられる前にform-elementsを削除することです:

$('#myForm').find("input[type='file']").each(function(){
    if ($(this).get(0).files.length === 0) {$(this).remove();}
});
var fData = new FormData($('#myForm')[0]);
...
2
    var fileNames = formData.getAll("filename[]");
    formData.delete("filename[]");
    jQuery.each(fileNames, function (key, fileNameObject) {
        if (fileNameObject.name) {
            formData.append("filename[]", fileNameObject);
        }
    });

これを試して !!

1

これは、入力フィールドが空かどうかを確認するのに役立ちます。空の場合、FormDataを作成する前に入力フィールドを無効にします。 FormDataを作成したら、「disabled」属性を削除します。他の答えとの違いは、「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);
0
Martin