jQuery : Uploader une image en AJAX avec un aperçu avant envoi

Le but de cet article est de :

  1. montrer comment afficher à l’utilisateur des informations sur le fichier sélectionné avec un input[type="file"], ce qui est très pratique pour éviter les erreurs. Et ceci sans plugin additionnel.
  2. uploader le fichier en AJAX à l’aide de jQuery.

La partie serveur, c’est-à-dire la réception du fichier et son stockage ne sera pas traitée dans cet article.

Le code HTML qui sera présenté sera le strict minimum fonctionnel, pour des questions de présentation, ma page de démonstration est plus élaborée. Voir la démonstration

Formulaire utilisé pour la démo de cet article

Formulaire utilisé pour la démo de cet article

Pour débuter, petit rappel sur l’envoi d’un formulaire (sans upload) en AJAX avec jQuery.

Envoi d’un formulaire avec jQuery

<form id="my_form" method="post" action="process_form.php">
    <input type="text" name="title">
    <textarea name="content"></textarea>
    <button type="submit">OK</button>
</form>
$(function () {
    $('#my_form').on('submit', function (e) {
        // On empêche le navigateur de soumettre le formulaire
        e.preventDefault();

        var $form = $(this);

        $.ajax({
            url: $form.attr('action'),
            type: $form.attr('method'),
            data: $form.serialize(),
            success: function (response) {
                // La réponse du serveur
            }
        });
    });
});
Inspection de la requête envoyée au serveur via Chrome Dev Tools

Inspection de la requête envoyée au serveur via Chrome Dev Tools

Ni plus ni moins pour un envoi de formulaire en AJAX. Pour davantage de détails et si vous n’êtes pas familier avec ce genre de code, j’ai fait un autre article « Envoyer un formulaire en AJAX avec jQuery et JSON« .

Ajouter un upload d’image

Ici mon exemple va se porter sur un upload d’image, mais peut s’adapter à n’importe quel autre type de fichier.

Voici le code qui supporte l’upload d’une image, en plus du reste du formulaire.

<form id="my_form" method="post" action="process_form.php" enctype="multipart/form-data">
    <input type="text" name="title">
    <textarea name="content"></textarea>
    <input type="file" name="image" accept="image/*">
    <button type="submit">OK</button>
</form>

On ajoute enctype="multipart/form-data" au formulaire, c’est obligatoire si on veut faire un upload de fichier. On ajoute également notre champ qui permet à l’utilisateur de sélectionner un fichier sur sa machine. On précise que seules les images sont acceptées.

$(function () {
    $('#my_form').on('submit', function (e) {
        // On empêche le navigateur de soumettre le formulaire
        e.preventDefault();

        var $form = $(this);
        var formdata = (window.FormData) ? new FormData($form[0]) : null;
        var data = (formdata !== null) ? formdata : $form.serialize();

        $.ajax({
            url: $form.attr('action'),
            type: $form.attr('method'),
            contentType: false, // obligatoire pour de l'upload
            processData: false, // obligatoire pour de l'upload
            dataType: 'json', // selon le retour attendu
            data: data,
            success: function (response) {
                // La réponse du serveur
            }
        });
    });
});

L’upload en AJAX est lié en fait au support du navigateur de l’API FormData. C’est supporté par les principaux navigateurs, du côté d’IE, c’est à partir de la version 10.

Mais le support de FormData ne suffit pas. Il faut aussi paramétrer la requête d’envoi :

  • contentType, quand non spécifié, la valeur prend 'application/x-www-form-urlencoded; charset=UTF-8' ce qui fonctionne pour un formulaire sans upload. Dans notre cas, il faut mettre la valeur à false.
  • processData, quand non spécifié, la valeur prend true. jQuery fait un pré-traitement avant envoi, il transforme les données en objet (voir le premier screenshot). Dans notre cas, il faut mettre la valeur à false.

Plus d’informations sur les paramètres de $.ajax().

Avec toute cette configuration, les données envoyées sont représentées comme ceci :

Inspection de la requête envoyée au serveur via Chrome Dev Tools

Inspection de la requête envoyée au serveur via Chrome Dev Tools

Ajouter un aperçu de l’image au moment de sa sélection

Pour ajouter un aperçu au moment de la sélection, un événement change sur le champ suffit. Tout le code à l’intérieur pour récupérer les données côté client est du Javascript natif. jQuery ne sert qu’à manipuler le DOM afin de montrer à l’utilisateur l’image choisie. Un console.log(files) vous donnera tout ce dont vous avez besoin.

$(function () {
    // A chaque sélection de fichier
    $('#my_form').find('input[name="image"]').on('change', function (e) {
        var files = $(this)[0].files;

        if (files.length > 0) {
            // On part du principe qu'il n'y qu'un seul fichier
            // étant donné que l'on a pas renseigné l'attribut "multiple"
            var file = files[0],
                $image_preview = $('#image_preview');

            // Ici on injecte les informations recoltées sur le fichier pour l'utilisateur
            $image_preview.find('.thumbnail').removeClass('hidden');
            $image_preview.find('img').attr('src', window.URL.createObjectURL(file));
            $image_preview.find('h4').html(file.name);
            $image_preview.find('.caption p:first').html(file.size +' bytes');
        }
    });

    // Bouton "Annuler" pour vider le champ d'upload
    $('#image_preview').find('button[type="button"]').on('click', function (e) {
        e.preventDefault();

        $('#my_form').find('input[name="image"]').val('');
        $('#image_preview').find('.thumbnail').addClass('hidden');
    });
});

Voir le résultat

En PHP, dans process_form.php, j’ai mis :

<?php
    $data['file'] = $_FILES;
    $data['text'] = $_POST;

    echo json_encode($data);
?>

Je vous invite à inspecter le code de la démo.

Vous aimerez aussi...

15 réponses

  1. Johnstyle dit :

    Une solution intéressante mais qui ne fonctionnera malheureusement pas sur une bonne partie des navigateurs encore utilisés…

  2. HOUEHA Francis dit :

    Je voudrais savoir pourquoi vous avez mis $(this)[0].files. Moi, en voulant appliquer votre code, j’ai fait $(‘#photoRegion »).files et photoRegion est l’id de mon champ de type file mais ça ne me renvoie rien.

  3. Tallio dit :

    Super le tuto / descriptif !!!

    Franchement merci. Pour les différents navigateurs on s’en fout un peu il suffit de faire un traitement en amont pour vérifier la version et renvoyer sur une page pour les losers qui restent bloqués dans les années 2000…

    Par contre,
    Il nous manque la suite :o)
    Quid de l’upload en traitement après…

    On aurait pu imaginer une suite géniale avec l’envoie, l’affichage en preview comme ici, puis une étape II vers le crop de l’image uploadée et enfin un enregistrement en BDD avec une copie de l’image cible dans un dossier X

    En tout cas bravo et merci.

  4. Anaclet TM dit :

    Excellent tuto. Je l’ai lu à la volé mais je peux dire déjà qu j’ai compris le principes car bien détaillée. Nous
    attendons la suite sur le traitement PHP.
    Coup de chapeau.

  5. cardona dit :

    Un excellent Tuto !!

    Je me permet de mettre en commentaire, la base de mon code pour déplacer l’image sur le serveur et ensuite de stocker l’image dans une BDD.

    // chargement de mes fonctions
    require_once(dirname(__FILE__).’/mylib.php’);

    $bdd = bdd();

    $data[‘file’] = $_FILES;
    $data[‘text’] = $_POST;

    // chaque image à un rôle dans mon code
    $role = « header »;

    // on récupère l’extension de l’image
    $extension_upload = strtolower( substr( strrchr($_FILES[‘image’][‘name’], ‘.’) ,1) );
    // la requête pour récupère la dernière ID de ma table
    $requete = ‘SELECT * FROM `wp_photo` ORDER BY `wp_photo`.`id_photo` DESC LIMIT 1 ‘;
    // on récupère l’id
    $resultat = $bdd->query($requete)->fetch();

    $id = $resultat[‘id_photo’];
    // on converti id en int
    $id = intval($id);
    // on incrémente L’id
    $id++;

    // Url de la photo
    $url = $_SERVER[‘DOCUMENT_ROOT’]. »/images/upload/ ».$id. ». ».$extension_upload;
    // la requête pour insérer dans ma bdd url de l’image
    $requete = « INSERT INTO tripcopress.wp_photo (id_photo, url, fonction) VALUES (‘ ».$id. »‘, ‘images/upload/ ».$id. ». ».$extension_upload. »‘, ‘ ».$role. »‘) »;

    $bdd->exec($requete);

    // on déplace l’image sur le serveur
    $resultat = move_uploaded_file($_FILES[‘image’][‘tmp_name’],$url);
    // on renvoie L’ID sur notre navigateur.
    echo json_encode($id);

  6. Malkiel dit :

    Pourrais tu m’envoyer un fichier ZIP contenant la démonstration que tu as faite

  7. jr2dallas dit :

    Salut et merci pour ton tuto qui vise exactement un problème technique récurrent, pas besoin de donner le code PHP pour le post traitement de l’image, il y a d’autres tuto pour ça.

    Par contre je voudrais réagir sur la réponse de @cardona qui donne son code pour garder l’image en BDD mais je voudrais avertir ceux qui voudrait le reprendre car une partie n’est pas correcte, je n’aime pas cette partie de solution:
    1/ Requête pour récupérer le dernier ID inséré
    2/ Insérer une ligne dans la table avec cet ID

    Ça ne va pas car:
    – Plusieurs insertions peuvent se produire en parallèle et on ne pourra pas savoir de cette manière quel est le dernier ID inséré
    – Pourquoi ne pas avoir mis un AUTOINCREMENT pour `id_photo` ? Cela permettrait d’éviter de récupérer le dernier ID
    – Finalement, pour renvoyer `id_photo`, il suffira d’utiliser LAST_INSERT_ID()

    En tout cas c’est du bon boulot ce site !
    Bonne continuation !

  8. Saluts dit :

    Cela m’a beaucoup aidé.
    Cependant lorsque Formdata n’est pas supporté ton code ne fonctionne pas à cause du paramétrage suivant :
    contentType: false, // obligatoire pour de l’upload
    processData: false, // obligatoire pour de l’upload
    Qu’il faut remettre au valeur d’origine.

    Dans l’attente de ta confirmation

  9. Noel Kenfack dit :

    C’est bon, il serait important d’ajouter à ce code les information sur la redimension de l’image qu’on a en apperçu.

  10. Farhani dit :

    Bonsoir

    Et si j’ai plusieur images à uploader?

  11. Farhani dit :

    Bonsoir,

    Sur mon site d’annonce http://www.okaz365.net, il est prévu que l’internaute peut ajouter plusieurs images (5) à son annonce.
    Donc est-ce que je peux avoir une version de code pour l’ajout de plusieurs images, bien sur avec aperçu de chacune d’elle avant upload.

    Amplement merçi

    A. Farhani

  12. sls dit :

    Bonjour j’ai un petit probleme dans mon application Web . comment faire pour recharger l’image dans mon formulaire ( exemple:si j’ai oublié un champ obligatoire ) ? merci en avance .

  13. Angel dit :

    Bonjour je souhaite comprendre le commentaire :
    // On part du principe qu’il n’y qu’un seul fichier
    // étant donné que l’on a pas renseigné l’attribut « multiple »

    Qu’est ce que <> ou doit-on l’indiquer et comment ?

    Je souhaite proposer l’upload de 3 photos name & id = photo_1, photo_2 et photo_3 avec trois aperçu apercu_1, apercu_2, apercu_3 (un pour chacune des trois images) un peu comme pour publier une annonce leboncoin.

    Comment dois je mi prendre ?

    Merci d’avance.

  14. Angel dit :

    Qu’est ce que « l’attribut « multiple » »*

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Image CAPTCHA

*