597 votes

Envoi de multipart/formdata avec jQuery.ajax

J'ai un problème pour envoyer un fichier à un script PHPscript côté serveur en utilisant la fonction ajax de jQuery. Il est possible d'obtenir la liste des fichiers avec $('#fileinput').attr('files') mais comment est-il possible d'envoyer ces données au serveur ? Le tableau résultant ( $_POST ) sur le serveur php-script est 0 ( NULL ) lors de l'utilisation de l'entrée fichier.

Je sais que c'est possible (bien que je n'aie pas trouvé de solutions jQuery jusqu'à présent, seulement du code Prototye ( http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html )).

Cela semble être relativement nouveau, alors ne dites pas que le téléchargement de fichiers est impossible via XHR/Ajax, car cela fonctionne parfaitement.

J'ai besoin de la fonctionnalité dans Safari 5, FF et Chrome seraient bien mais ne sont pas essentiels.

Mon code pour l'instant est :

$.ajax({
    url: 'php/upload.php',
    data: $('#file').attr('files'),
    cache: false,
    contentType: 'multipart/form-data',
    processData: false,
    type: 'POST',
    success: function(data){
        alert(data);
    }
});

0 votes

Malheureusement, l'utilisation de l'objet FormData ne fonctionne pas sur IE<10.

0 votes

@GarciaWebDev supposément vous pouvez utiliser un polyfill avec Flash pour supporter la même API. Voir aussi github.com/Modernizr/Modernizr/wiki/ pour plus d'informations.

0 votes

Possible duplicate .

920voto

Raphael Schweikert Points 6380

À partir de Safari 5/Firefox 4, le plus simple est d'utiliser la fonction FormData classe :

var data = new FormData();
jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file-'+i, file);
});

Donc maintenant vous avez un FormData prêt à être envoyé avec le XMLHttpRequest.

jQuery.ajax({
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
});

Il est impératif que vous définissiez le contentType option pour false ce qui permet à jQuery de ne pas ajouter d'élément Content-Type pour vous, sinon, la chaîne de délimitation sera manquante. De même, vous devez laisser le champ processData défini à false, sinon, jQuery essaiera de convertir votre code d'accès en un code d'accès à l'information. FormData en une chaîne de caractères, ce qui échouera.

Vous pouvez maintenant récupérer le fichier en PHP en utilisant :

$_FILES['file-0']

(Il n'y a qu'un seul fichier, file-0 sauf si vous avez spécifié l'option multiple sur votre entrée de fichier, auquel cas, les nombres s'incrémenteront avec chaque fichier).

Utilisation de la Émulation de FormData pour les navigateurs plus anciens

var opts = {
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
};
if(data.fake) {
    // Make sure no text encoding stuff is done by xhr
    opts.xhr = function() { var xhr = jQuery.ajaxSettings.xhr(); xhr.send = xhr.sendAsBinary; return xhr; }
    opts.contentType = "multipart/form-data; boundary="+data.boundary;
    opts.data = data.toString();
}
jQuery.ajax(opts);

Créer des FormData à partir d'un formulaire existant

Au lieu d'itérer manuellement les fichiers, l'objet FormData peut également être créé avec le contenu d'un objet de formulaire existant :

var data = new FormData(jQuery('form')[0]);

Utiliser un tableau natif PHP au lieu d'un compteur

Il suffit de donner le même nom à vos éléments de fichier et de terminer le nom entre parenthèses :

jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file[]', file);
});

$_FILES['file'] sera alors un tableau contenant les champs de téléchargement de chaque fichier téléchargé. Je recommande cette solution plutôt que ma solution initiale car elle est plus simple à itérer.

2 votes

Il existe également un Émulation de FormData ce qui rendra le portage de cette solution vers des navigateurs plus anciens assez simple. Tout ce que vous avez à faire est de vérifier si data.fake et définir les contentType manuellement, ainsi qu'en remplaçant xhr pour utiliser la propriété sendAsBinary() .

0 votes

Bonjour @Raphael Schwekert, nous avons essayé cela, mais cela ne semble pas fonctionner ( stackoverflow.com/questions/10215425/ ). Où fixez-vous la limite pour le multipart/form-data ?

2 votes

Pourriez-vous préciser "ne semble pas fonctionner" ? Qu'avez-vous essayé exactement ? Le site FormData l'émulation ou la classe native ? Lorsque l'on utilise l'émulation, la limite se trouve à data.boundary donc vous ajouteriez xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); xhr.send = xhr.sendAsBinary; return xhr; }, aux options et utiliser "multipart/form-data; boundary="+data.boundary pour le contentType option.

59voto

Asad Malik Points 98

Regardez mon code, il fait le travail pour moi

$( '#formId' )
  .submit( function( e ) {
    $.ajax( {
      url: 'FormSubmitUrl',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );

0 votes

Cela a parfaitement fonctionné et c'est très simple à mettre en œuvre.

0 votes

Ce script fait ce dont j'ai besoin pour soumettre un petit formulaire et il fonctionne à une exception près... il utilise l'URL de l'action d'un autre formulaire sur la même page. J'ai l'URL correcte dans le script et aucune URL dans le formulaire associé. Pourquoi fait-il ce changement et comment puis-je le forcer à utiliser la bonne ?

0 votes

Cela fonctionne parfaitement bien si vous mettez e.preventDefault(); avant l'ajax. Merci

51voto

ajmicek Points 324

Je voulais juste ajouter un peu à l'excellente réponse de Raphael. Voici comment faire en sorte que PHP produise la même chose $_FILES que vous utilisiez ou non JavaScript pour la soumission.

Formulaire HTML :

<form enctype="multipart/form-data" action="/test.php" 
method="post" class="putImages">
   <input name="media[]" type="file" multiple/>
   <input class="button" type="submit" alt="Upload" value="Upload" />
</form>

PHP produit ceci $_FILES lorsqu'il est soumis sans JavaScript :

Array
(
    [media] => Array
        (
            [name] => Array
                (
                    [0] => Galata_Tower.jpg
                    [1] => 518f.jpg
                )

            [type] => Array
                (
                    [0] => image/jpeg
                    [1] => image/jpeg
                )

            [tmp_name] => Array
                (
                    [0] => /tmp/phpIQaOYo
                    [1] => /tmp/phpJQaOYo
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 258004
                    [1] => 127884
                )

        )

)

Si vous faites une amélioration progressive, en utilisant le JS de Raphael pour soumettre les fichiers...

var data = new FormData($('input[name^="media"]'));     
jQuery.each($('input[name^="media"]')[0].files, function(i, file) {
    data.append(i, file);
});

$.ajax({
    type: ppiFormMethod,
    data: data,
    url: ppiFormActionURL,
    cache: false,
    contentType: false,
    processData: false,
    success: function(data){
        alert(data);
    }
});

... c'est ce que PHP $_FILES ressemble, après avoir utilisé ce JavaScript pour soumettre :

Array
(
    [0] => Array
        (
            [name] => Galata_Tower.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpAQaOYo
            [error] => 0
            [size] => 258004
        )

    [1] => Array
        (
            [name] => 518f.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpBQaOYo
            [error] => 0
            [size] => 127884
        )

)

C'est un joli tableau, et en fait ce que certaines personnes transforment $_FILES mais je trouve qu'il est utile de travailler avec la même chose. $_FILES indépendamment du fait que JavaScript ait été utilisé pour la soumission. Voici donc quelques modifications mineures apportées au JS :

// match anything not a [ or ]
regexp = /^[^[\]]+/;
var fileInput = $('.putImages input[type="file"]');
var fileInputName = regexp.exec( fileInput.attr('name') );

// make files available
var data = new FormData();
jQuery.each($(fileInput)[0].files, function(i, file) {
    data.append(fileInputName+'['+i+']', file);
});

(Modification du 14 avril 2017 : j'ai supprimé l'élément de formulaire du constructeur de FormData() -- cela a corrigé ce code dans Safari).

Ce code fait deux choses.

  1. Récupère le input automatiquement, ce qui rend le HTML plus facile à maintenir. Maintenant, tant que form a la classe putImages, tout le reste est pris en charge automatiquement. C'est-à-dire que la classe input ne doit pas nécessairement porter un nom particulier.
  2. Le format de tableau que le HTML normal soumet est recréé par le JavaScript dans la ligne data.append. Notez les parenthèses.

Avec ces changements, la soumission avec JavaScript produit maintenant exactement la même chose. $_FILES comme soumission avec un simple HTML.

0 votes

J'ai eu le même problème avec Safari. Merci pour l'astuce !

47voto

evandro777 Points 141

J'ai juste construit cette fonction en me basant sur des informations que j'ai lues.

Utilisez-le comme si vous utilisiez .serialize() au lieu de cela, mettez simplement .serializefiles(); .
Je travaille ici dans mes tests.

//USAGE: $("#form").serializefiles();
(function($) {
$.fn.serializefiles = function() {
    var obj = $(this);
    /* ADD FILE TO PARAM AJAX */
    var formData = new FormData();
    $.each($(obj).find("input[type='file']"), function(i, tag) {
        $.each($(tag)[0].files, function(i, file) {
            formData.append(tag.name, file);
        });
    });
    var params = $(obj).serializeArray();
    $.each(params, function (i, val) {
        formData.append(val.name, val.value);
    });
    return formData;
};
})(jQuery);

2 votes

J'ai essayé de faire fonctionner cette fonction, mais il semble qu'elle ne reconnaisse pas serializefiles() comme une fonction, malgré cette définition en haut de la page.

1 votes

Cela fonctionne très bien pour moi. obtenir des données avec var data = $("#avatar-form").serializefiles(); envoyer ceci via un paramètre de données ajax et analyser avec express formidable : form.parse(req, function(err, fields, files){ merci pour cet extrait de code :)

24voto

Devin Venable Points 96

Si votre formulaire est défini dans votre HTML, il est plus facile de passer le formulaire dans le constructeur que d'itérer et d'ajouter des images.

$('#my-form').submit( function(e) {
    e.preventDefault();

    var data = new FormData(this); // <-- 'this' is your form element

    $.ajax({
            url: '/my_URL/',
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',     
            success: function(data){
            ...

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