Uploader un fichier en Javascript avec fetch

Dans cet article on va voir comment uploader un fichier en utilisant Javascript et l’API fetch. On partira d’un exemple en PHP que l’on fera évoluer pour atteindre le résultat souhaité.

Le code complet est disponible ici

{% github jean-smaug/demo-file-upload no-readme %}

Version PHP

Considerons l’exemple suivant :

upload-php

Après la soumission du formulaire, on voit que l’URL change vers upload.php. Cette page enregistre le fichier uploadé et affiche un message de succès. Il est ensuite redirigé vers la page index.html. Ce système fonctionne, mais il implique trois chargements de pages.

Regardons le code utilisé.

<!DOCTYPE html>
<html>
  <head>
    <!-- ... -->
  </head>
  <body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
      <input type="file" name="background" />
      <button type="submit">Envoyer</button>
    </form>
  </body>
</html>

Ce code HTML est standard. Le seul point d’attention est l’attribut enctype dont la valeur doit obligatoirement être multipart/form-data pour pouvoir uploader un fichier.

<?php
    header("Access-Control-Allow-Origin: *");

    /* La variable superglobale $_FILES nous donne accès aux fichiers
    qui ont été uploadés. La clé "background" fait référence à
    l'attribut name de <input name="background" /> */
    $file = $_FILES["background"];
    $isFileUploaded = move_uploaded_file($file["tmp_name"], __DIR__ . "/../storage/" . $file["name"]);

    if($isFileUploaded === false) {
        http_response_code(500);
        echo "Problème serveur";
    }
    else {
        http_response_code(201);
        readfile('success.html');
    }

    // Redirection sur index.html après 2 secondes
    header("Refresh:2; url=index.html", true, 303);

La partie serveur récupère le fichier et de le déplace dans le dossier storage. Une fois le traitement terminé on affiche un message à l’utilisateur puis on le redirige vers la page index.html après deux seconde. L’essentiel étant de retenir que le nom donné à l’input à son importance et sera utilisé par le code PHP pour récupérer le fichier.

APIs web

Nous allons avoir besoin de deux API web : FormData et Fetch.

FormData

Form Data

FormData dispose d’un très bon support de la part des navigateurs.

Cette API permet de représenter un formulaire côté Javascript. Ce formulaire aura un encodage de type multipart/form-data, donc il n’est pas nécessaire de le préciser dans le HTML. Ce type d’encodage est nécessaire dès lors que l’on souhaite uploader un fichier.

Fetch

Fetch

Aussi surprenant que cela puisse paraître, c’est Internet Explorer qui nous casse les ligaments. Il existe deux solutions pour contourner le problème de compatibilité :

  • Utiliser un polyfill
  • L’astuce que je présente plus bas

L’API fetch est la façon moderne pour faire des requêtes HTTP. Elle repose sur les promesses (Promise). On peut utiliser les promesses avec plusieurs syntaxe, ici, on utilisera async/await.

/* Le mot-clé async signifie que le mot clé await va être
utilisé dans le corps de la fonction */
async function getLucky() {
  /* Le mot clé await signifie que l'on attend le resultat d'une
   fonction, ici somethingThatTakesTime. On ne sait pas combien
   de temps mettra la fonction à répondre donc on attend le resultat
   avant d'executer les instructions suivantes */
  const luck = await somethingThatTakesTime()

  return luck
}

Mon explication est très succinte, car je ne veux pas trop digresser. Cependant, je vous conseille cette vidéo de Grafikart pour en apprendre plus.

Version Javascript

<!DOCTYPE html>
<html>
  <head>
    <!-- ... -->
  </head>
  <body>
    <form id="form">
      <input type="file" name="background" />
      <button type="submit">Envoyer</button>
    </form>
    <p id="message"></p>
    <script src="./app.js"></script>
  </body>
</html>

La partie HTML reste sensiblement la même. Les anciens attributs du form on laissé place à un id. On ajoute un paragraphe pour afficher le message de succès ou d’échec. Enfin, on charge notre script.

/* On récupère les éléments form et message */
const form = document.getElementById("form")
const message = document.getElementById("message")

/* Lors de la soumission du formulaire on previent
le comportement par défaut */
form.addEventListener("submit", async function (e) {
  e.preventDefault()

  /* L'astuce pour IE, si vous n'utilisez pas de polyfill, consiste
  à inviter l'utilisateur à utiliser un autre navigateur */
  if (!window.fetch || !window.FormData) {
    alert(
      "Tu crois que c'est du respect mon garçon ? Est ce que tu crois que c'est du respect d'utiliser un navigateur archaïque ?"
    )
    return
  }

  /* Lorsque l'on instancie FormData on peut lui passer un élément
  form en paramètre. De cette façon, FormData peut detecter chaque
  input du formulaire et leur valeur.
  Ici, this fait référence à form */
  const formData = new FormData(this)

  try {
    /* fetch() prend en 1er paramètre l'url et en second paramètre
    les options. Ici, nous indiquons que notre requête sera en POST
    et que le corps de la requête sera constitué de nos formData. */
    await fetch("http://localhost:4000/upload.php", {
      method: "POST",
      body: formData,
    })

    // On affiche un message suivant le résultat de la requête
    message.innerText = "Fichier uploadé avec succès \\o/"
  } catch (error) {
    message.innerText =
      "C'est la cata, c'est la cata, c'est la catastrophe /o\\"
  }

  // On réinitialise le formulaire
  this.reset()

  // On efface le message après deux secondes
  setTimeout(() => {
    message.innerText = ""
  }, 2000)
})

C’est ainsi que l’on arrive à ce résultat.

upload-js

La dernière étape est une optimisation du serveur. Les commentaires représentent les lignes devenue inutile et que nous pouvons donc supprimer.

<?php
    header("Access-Control-Allow-Origin: *");

    $file = $_FILES["background"];
    $isFileUploaded = move_uploaded_file($file["tmp_name"], __DIR__ . "/../storage/" . $file["name"]);

    if($isFileUploaded === false) {
        http_response_code(500);
        // echo "Problème serveur";
    }
    else {
        http_response_code(201);
        // readfile('success.html');
    }

    // header("Refresh:2; url=index.html", true, 303);

Conclusion

La combinaison des APIs FormData et fetch, rend l’upload de fichier très simple. On évite le rechargement de page ce qui améliore l’expérience utilisateur.

Pour effectuer des requête HTTP, vous pouvez très bien utiliser Fetch, comme présenté dans l’article mais vous devriez jeter un oeil à :

  • Axios, une librairie basée sur XMLHttpRequest, l’ancètre de fetch. Elle est donc compatible avec IE.
  • Ky, une librairie qui englobe fetch et propose de nouvelles fonctionnalités. Ky comparé à axios par le créateur de Ky.

Enfin, il est tout à fait possible d’utiliser ces principes avec des librairies comme React ou Vue.js. Il faudra simplement utiliser les références sur vos formulaires.

Merci de m’avoir lu.

Maxime Blanc


© 2020 Maxime Blanc. Tous droits réservés.