web-dev-qa-db-fra.com

Safari 11.1: la soumission du formulaire ajax/XHR échoue lorsque l'entrée [type = fichier] est vide

UPDATE: à partir de Webkit build r230963 , ce problème a été résolu dans Webkit.

===========

Depuis la récente mise à jour de Safari 11.1 sur macOS et iOS, ainsi que dans Safari Technology Preview 11.2, les appels $.ajax dans mon application Web échouent lorsqu'un champ input[type=file] ne contient aucun fichier (cela n'est pas obligatoire dans mon formulaire). Pas d'échec lorsque le champ fait a un fichier choisi.

Le rappel error de ajax s'exécute et la console Safari contient le message suivant: Failed to load resource: The operation couldn’t be completed. Protocol error. Je suis HTTPS et soumets à un emplacement sur le même domaine (et serveur) également via HTTPS.

Avant la mise à jour 11.1, l’appel $.ajax a été soumis sans problème si aucun fichier n’était choisi. Les dernières versions de Chrome et Firefox n'ont pas de problèmes.

Parties pertinentes de mon code:

L'entrée:

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

Le 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 }
});

En tant que solution temporaire (j'espère), je détecte un champ de fichier vide et le supprime de formData avant l'appel ajax et tout fonctionne comme prévu/avant:

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

Est-ce que je fais quelque chose de mal, y a-t-il un problème avec Safari ou un changement de Safari doit-il être pris en compte maintenant dans les appels ajax?

38
Matt.

A partir de Webkit build r230963 , ce problème a été résolu dans Webkit. J'ai téléchargé et exécuté cette version et confirmé que le problème était résolu. Pas idée quand une version publique sera disponible pour Safari qui contient ce correctif.

5
Matt.

UPDATE: L'ancienne réponse ne fonctionne PAS dans Firefox.

Firefox renvoie juste une chaîne vide pour FormData.get() dans un champ de fichier vide (au lieu de l'objet File dans les autres navigateurs). Ainsi, lorsqu’on utilise une ancienne solution de contournement, <input type="file"> vide sera envoyé comme en tant que <input type="text"> vide. Malheureusement, il n'y a aucun moyen de distinguer un fichier vide d'un texte vide après la création d'un objet FormData.

Utilisez cette solution à la place:

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)

Démo en direct: https://jsfiddle.net/ypresto/05Lc45eL/

Pour un environnement autre que 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')
})

Pour Rails (Rails-ujs/jQuery-ujs): https://Gist.github.com/ypresto/cabce63b1f4ab57247e1f836668a00a5


Ancienne réponse:

Filtrer FormData (dans la réponse de Ravichandra Adiga) est préférable car il ne manipule aucun DOM.

Mais l'ordre des pièces dans FormData est garanti d'être le même ordre pour entrer des éléments dans le formulaire , selon la spécification <form>. Cela pourrait causer un autre bogue si quelqu'un s'appuyait sur cette spécification.

L'extrait ci-dessous conserve l'ordre FormData et la partie vide.

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
}

Voici un exemple en direct: https://jsfiddle.net/ypresto/y6v333bq/

Pour Rails, voir ici: https://github.com/Rails/rails/issues/32440#issuecomment-381185380

(REMARQUE: Safari sur iOS 11.3 présente ce problème, mais pas 11.2.)

18
ypresto

Pour résoudre ce problème, je supprime complètement le fichier de type d'entrée de DOM à l'aide de la méthode jQuery remove ().

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

J'ai travaillé sur ce qui semble être le même problème dans un programme Perl

Le traitement de multipart/form-data dans Perl appelle Apache-error avec les périphériques Apple, lorsqu'un élément form-file-file est vide } _

La solution consiste à supprimer les éléments de formulaire avant l'attribution des données de formulaire: 

$('#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);
        }
    });

Essaye ça !!

1
Ravichandra Adiga

Cela me permet de vérifier si le champ de saisie est vide. S'il est vide, désactivez le champ de saisie avant de créer le FormData. Après avoir créé le FormData, supprimez l'attribut "disabled". La différence avec les autres réponses est que je recherche "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