40 votes

Safari 11.1 : la soumission d'un formulaire ajax/XHR échoue lorsque input[type=file] est vide

UPDATE : A 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 l'aperçu technologique Safari 11.2, l'option $.ajax dans mon application web échouent lorsqu'un input[type=file] n'a pas de fichier choisi (il n'est pas requis dans mon formulaire). Pas d'échec lorsque le champ fait ont choisi un dossier.

El error rappel 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 en HTTPS et je soumets à un emplacement sur le même domaine (et serveur) également en HTTPS.

Avant la mise à jour 11.1, le $.ajax L'appel a été soumis sans problème lorsqu'aucun fichier n'a été choisi. Les dernières versions de Chrome et de Firefox ne posent aucun problème.

Parties pertinentes de mon code :

L'entrée :

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

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

Comme solution temporaire (en espérant qu'elle le soit), je détecte un champ de fichier vide et le supprime de formData avant le ajax appel 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, est-ce qu'il y a un problème avec Safari, ou y a-t-il un changement dans Safari qui doit être pris en compte dans les appels ajax ?

1 votes

Excellente solution temporaire, mais ne fonctionne pas sous Safari 11 si elle est appliquée à tous les navigateurs Safari. Avez-vous trouvé une "vraie" solution ?

0 votes

@Karem - Merci pour cette info ! Heureusement, les utilisateurs de mon application ne seront pas sous Safari 11 mais c'est bon à savoir. Et non, pas encore de solution. J'ai déposé un rapport de radar/bug auprès d'Apple et ils viennent de demander aujourd'hui un diagnostic du système que je vais envoyer.

0 votes

J'ai eu le même problème et cet article m'a beaucoup aidé. Merci. Pour information, je crois que FormDeta#delete() n'est supporté que dans Safari 11 ou plus. Vous devez donc ajouter un contrôle de version. caniuse.com/#feat=xhr2 Quoi qu'il en soit, merci pour vos informations.

18voto

ypresto Points 13

MISE À JOUR : L'ancienne réponse ne fonctionne PAS dans Firefox.

Le retour de Firefox juste une chaîne vide para FormData.get() sur un champ de fichier vide (au lieu de l'objet File dans les autres navigateurs). Ainsi, en utilisant l'ancienne solution de contournement, les champs vides <input type="file"> sera envoyée comme si elle était vide <input type="text"> . Malheureusement, il n'y a aucun moyen de distinguer un fichier vide d'un texte vide après la création de l'objet FormData.

Utilisez plutôt cette solution :

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émonstration en direct : https://jsfiddle.net/ypresto/05Lc45eL/

Pour l'environnement non-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


Vieille réponse :

Le filtrage de FormData (dans la réponse de Ravichandra Adiga) est meilleur car il ne manipule pas de DOM.

Mais l'ordre des éléments dans FormData est garanti être le même que celui des éléments d'entrée dans le formulaire selon <form> les spécifications. Cela pourrait causer un autre bogue si quelqu'un s'appuie sur cette spécification.

Le snippet ci-dessous gardera l'ordre des 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
}

L'exemple en direct est ici : https://jsfiddle.net/ypresto/y6v333bq/

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

(REMARQUE : le Safari d'iOS 11.3 présente ce problème, mais pas celui de 11.2).

1 votes

Merci @ypresto. Je vais enquêter sur ce point ! Par ailleurs, iOS 11.2 utilise Safari 11.0.3, c'est pourquoi le problème n'est pas présent dans cette version.

0 votes

Merci beaucoup ! Nous avons l'erreur nginx 400 sur les navigateurs iphone et cette astuce nous a sauvé !

6voto

mani_007 Points 590

Pour contourner le problème, je supprime complètement le fichier de type d'entrée du DOM en utilisant la méthode jQuery remove().

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

1 votes

Le problème est que je devrais restaurer le champ dans la méthode always(). Une fois l'appel AJAX effectué, l'utilisateur doit à nouveau interagir avec le formulaire, y compris les entrées de fichiers.

0 votes

Je comprends que vous devriez peut-être les rajouter dans le callback de la méthode Ajax, cette solution de contournement a fonctionné pour moi.

3 votes

Vous pourriez simplement ajouter un attribut "désactivé" à l'entrée, afin qu'elle ne soit pas collectée dans les données du formulaire ; vous pouvez ensuite supprimer l'attribut lorsque vous avez des fichiers à télécharger.

5voto

Matt. Points 939

A partir de Webkit build r230963 Si vous n'avez pas de problème, ce problème a été résolu dans Webkit. J'ai téléchargé et exécuté cette version et j'ai confirmé que le problème est résolu. Je ne sais pas quand une version publique contenant cette correction sera disponible pour Safari.

2voto

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

Le traitement de multipart/form-data en Perl invoque Apache-error avec les appareils Apple, lorsqu'un élément du fichier de formulaire est vide.

La solution consiste à supprimer les éléments du formulaire avant que les données du formulaire ne soient attribuées :

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

0 votes

C'est totalement vrai, tu m'as sauvé la vie ! C'est un must pour Safari si vous téléchargez des fichiers avec Ajax.

1voto

    var fileNames = formData.getAll("filename[]");
    formData.delete("filename[]");
    jQuery.each(fileNames, function (key, fileNameObject) {
        if (fileNameObject.name) {
            formData.append("filename[]", fileNameObject);
        }
    });

Essayez-le !

0 votes

Ma solution temporaire fonctionne pour l'instant jusqu'à ce que ce problème soit résolu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X