web-dev-qa-db-fra.com

Envoi de multipart/formdata avec jQuery.ajax

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

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

Cela semble être relativement nouveau, évitez donc de mentionner que le téléchargement de fichier serait impossible via XHR/Ajax, car cela fonctionne définitivement.

J'ai besoin de la fonctionnalité de Safari 5, FF et Chrome serait agréable mais pas indispensable.

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);
    }
});
504
zoku

À partir de Safari 5/Firefox 4, il est plus simple d’utiliser la classe FormData:

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

Alors maintenant, vous avez un objet FormData, prêt à être envoyé avec 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 de définir l'option contentType sur false, forçant jQuery à ne pas ajouter d'en-tête Content-Type, sinon la chaîne de limite lui manquera . Vous devez également laisser l'indicateur processData défini sur false, sinon, jQuery essaiera de convertir votre FormData en une chaîne, ce qui échouera.

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

$_FILES['file-0']

(Il n'y a qu'un seul fichier, file-0, à moins que vous n'ayez spécifié l'attribut multiple dans votre entrée de fichier, auquel cas les numéros seront incrémentés avec chaque fichier.)

Utilisation de l'émulation FormData pour les anciens navigateurs

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

Create 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]);

Utilisez un tableau natif PHP au lieu d'un compteur

Nommez simplement les éléments de votre fichier et mettez 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 fichier pour chaque fichier téléchargé. En fait, je le recommande par rapport à ma solution initiale, car il est plus simple de le parcourir.

814

Je voulais juste ajouter un peu à l'excellente réponse de Raphaël. Voici comment obtenir PHP pour produire le même $_FILES, que vous utilisiez JavaScript ou non.

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 ce $_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, utilisez JS de Raphaël 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);
    }
});

... voici à quoi ressemble le tableau $_FILES de PHP, après avoir utilisé ce code JavaScript pour le 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 en $_FILES, mais je trouve utile de travailler avec le même $_FILES, que JavaScript ait été utilisé ou non. Alors, voici quelques changements mineurs 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);
});

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

Ce code fait deux choses.

  1. Récupère automatiquement l'attribut input name, ce qui rend le code HTML plus facile à gérer. Maintenant, tant que form a la classe putImages, tout le reste est traité automatiquement. C'est-à-dire que la input n'a pas besoin de nom spécial.
  2. Le format de tableau soumis par HTML normal est recréé par JavaScript dans la ligne data.append. Notez les crochets.

Avec ces modifications, la soumission avec JavaScript génère désormais exactement le même tableau $_FILES que celui soumis avec du HTML simple.

46
ajmicek

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();
  } );
44
Asad Malik

Je viens de construire cette fonction en fonction des informations que j'ai lues.

Utilisez-le comme si vous utilisiez .serialize(); mettez plutôt .serializefiles();.
Travailler 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);
44
evandro777

Si votre formulaire est défini dans votre code HTML, il est plus facile de le transmettre au constructeur que de le parcourir et de l'ajouter.

$('#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){
            ...
23
Devin Venable

La réponse de Devin Venable était proche de ce que je voulais, mais je voulais un qui pourrait fonctionner sur plusieurs formulaires et utiliser l'action déjà spécifiée dans le formulaire afin que chaque fichier soit placé au bon endroit.

Je voulais aussi utiliser la méthode on () de jQuery afin d'éviter d'utiliser .ready ().

Cela m’a amené à ceci: (Remplacez formSelector par votre sélecteur jQuery)

$(document).on('submit', formSelecter, function( e ) {
        e.preventDefault();
    $.ajax( {
        url: $(this).attr('action'),
        type: 'POST',
        data: new FormData( this ),
        processData: false,
        contentType: false
    }).done(function( data ) {
        //do stuff with the data you got back.
    });

});
6
Karl Henselin

La classe FormData fonctionne, mais dans iOS Safari (du moins sur l'iPhone), je n'ai pas pu utiliser la solution de Raphael Schweikert telle quelle.

Mozilla Dev a une page Nice sur la manipulation d'objets FormData .

Alors, ajoutez un formulaire vide quelque part dans votre page, en spécifiant le type:

<form enctype="multipart/form-data" method="post" name="fileinfo" id="fileinfo"></form>

Ensuite, créez un objet FormData en tant que:

var data = new FormData($("#fileinfo"));

et procédez comme dans le code de Raphael .

1
topkara

Les anciennes versions de IE ne prennent pas en charge FormData (la liste complète de prise en charge du navigateur pour FormData est ici: https://developer.mozilla.org/en-US/docs/Web/API/FormData ).

Soit vous pouvez utiliser un plugin jquery (par exemple, http://malsup.com/jquery/form/#code-samples ) ou vous pouvez utiliser une solution basée sur IFrame pour publier des données de formulaire multipart via ajax: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

0
sudip

Si l'entrée de fichier name indique un tableau et des indicateurs multiple, et que vous analysez l'intégralité de form avec FormData, il n'est pas nécessaire de procéder de manière itérative append() à l'entrée des dossiers. FormData gérera automatiquement plusieurs fichiers.

$('#submit_1').on('click', function() {
  let data = new FormData($("#my_form")[0]);

  $.ajax({
    url: '/path/to/php_file',
    type: 'POST',
    data: data,
    processData: false,
    contentType: false,
    success: function(r) {
      console.log('success', r);
    },
    error: function(r) {
      console.log('error', r);
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="my_form">
  <input type="file" name="multi_img_file[]" id="multi_img_file" accept=".gif,.jpg,.jpeg,.png,.svg" multiple="multiple" />
  <button type="button" name="submit_1" id="submit_1">Not type='submit'</button>
</form>

Notez qu'un button type="button" normal est utilisé, pas type="submit". Cela montre qu'il n'y a aucune dépendance à utiliser submit pour obtenir cette fonctionnalité.

L'entrée résultante $_FILES ressemble à ceci dans Chrome outils de développement:

multi_img_file:
  error: (2) [0, 0]
  name: (2) ["pic1.jpg", "pic2.jpg"]
  size: (2) [1978036, 2446180]
  tmp_name: (2) ["/tmp/phphnrdPz", "/tmp/phpBrGSZN"]
  type: (2) ["image/jpeg", "image/jpeg"]

Remarque: dans certains cas, certaines images seront correctement téléchargées lorsqu’elles seront téléchargées en tant que fichier unique, mais elles échoueront lorsqu’elles seront téléchargées dans un ensemble de plusieurs fichiers. Le symptôme est que PHP signale $_POST et $_FILES vides sans que AJAX ne génère aucune erreur. Un problème survient avec Chrome 75.0.3770.100 et PHP 7.0. Cela ne semble se produire qu'avec 1 sur plusieurs dizaines d'images dans mon ensemble de test.

0
OXiGEN

Jours Nova, vous n'avez même pas besoin de jQuery :) récupérer le tableau de support API

let result = fetch('url', {method: 'POST', body: new FormData(documemt.querySelector("#form"))})
0
Alex Nikulin

Un problème que j’ai rencontré aujourd’hui mérite d’être signalé à propos de ce problème: si l’URL de l’appel ajax est redirigé, l’entête du type de contenu: 'multipart/form-data' peut être perdu.

Par exemple, je publiais sur http://server.com/context?param=x

Dans l'onglet Réseau de Chrome, j'ai trouvé l'en-tête multipart approprié pour cette demande, puis une redirection 302 vers http://server.com/context/?param=x (notez la barre oblique après le contexte).

Au cours de la redirection, l'en-tête multipart a été perdu. Assurez-vous que les demandes ne sont pas redirigées si ces solutions ne fonctionnent pas pour vous.

0
james

Toutes les solutions ci-dessus sont belles et élégantes, mais l'objet FormData () n'attend aucun paramètre, mais utilise append () après l'instancier, comme ce qui est écrit ci-dessus:

formData.append (val.name, val.value);

0
szatti1489