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?
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.
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.)
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();
}
});
J'ai travaillé sur ce qui semble être le même problème dans un programme Perl
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]);
...
var fileNames = formData.getAll("filename[]");
formData.delete("filename[]");
jQuery.each(fileNames, function (key, fileNameObject) {
if (fileNameObject.name) {
formData.append("filename[]", fileNameObject);
}
});
Essaye ça !!
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);