Adloun

Projet : la Librairie en application Vue

Cours · Vue : construire une application web (école d'ingénieur), chapitre 12 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun

Au livre HTML, la Librairie du Campus était une suite de pages structurées ; au livre CSS, elle a pris sa charte et sa mise en page ; au livre JavaScript, elle est devenue interactive, à la main. Dans ce livre, elle a été reconstruite en composants, puis dotée d'un routeur, d'un état partagé, de tests. Il lui manque une dernière page, la plus importante pour une librairie : celle où l'on commande.

Ce chapitre termine l'application. On écrit la page de commande (validation, envoi au serveur, refus, confirmation), on fait le tour de ses états (chargement, erreur, vide), on la vérifie de trois façons (tests de composants, tests de bout en bout, revue d'accessibilité), puis on la construit pour la production. Le code complet est dans code/ch12/librairie/ : c'est le point de départ du livre suivant, où le serveur d'essai sera remplacé par un vrai serveur Java.

Objectifs du chapitre

  • Assembler une application complète à partir des outils du livre : composants, composables, routeur, stores.
  • Écrire un formulaire qui guide la saisie, envoie ses données au serveur et traite chacune de ses réponses.
  • Gérer et vérifier les états de chaque page : chargement, erreur, vide, succès.
  • Vérifier l'application à trois niveaux : composants, parcours complets, accessibilité.
  • Relire l'application avec un œil critique et savoir ce qui viendrait ensuite.

12.1 Le cahier des charges

L'application finale doit permettre à un étudiant :

Le serveur est celui du livre JavaScript, outils/serveur.mjs. Pour une commande, il attend POST /api/commandes avec un corps JSON :


{
  "lignes": [{ "isbn": "9782956300069", "quantite": 2 }],
  "retrait": "campus",
  "nom": "Ada Lovelace",
  "courriel": "ada@example.org"
}

Il répond 201 et { "numero": 1001, "total": 17.8 } si tout va bien ; 400 et la liste des erreurs ({ "erreurs": [ … ] }) s'il refuse (stock insuffisant, courriel invalide…) ; 500 quand on le lance avec --panne. Il recalcule le total lui-même : le prix affiché par la page n'est qu'une information, le serveur seul fait foi.

12.2 L'architecture

Chaque route mène à une page (src/pages/), chargée à la demande sauf le catalogue et la page introuvable. Les pages se construisent avec les composants de src/components/ et s'appuient sur deux stores : le catalogue, chargé une fois, et le panier, conservé dans localStorage. La fiche et la commande s'adressent directement au serveur, par deux composables. Le projet compte :


src/
  main.js, App.vue, router.js       démarrage, gabarit commun, routes et gardes
  pages/        5 pages             une par route
  components/   7 composants        en-tête, pied, carte, liste, recherche, tri, panier
  composables/  4 composables       useFetch, useEnvoi, useStockage, useRaccourci
  stores/       2 stores            catalogue, panier
  utils/        format.js           prix en euros, texte sans accents
  assets/       style.css           la feuille du livre CSS
tests/          7 fichiers, 20 tests (Vitest)
e2e/            2 fichiers, 4 tests (Playwright)
deploiement/    nginx.conf

12.3 La page de commande

12.3.1 Y arriver

La page du panier propose désormais de passer la commande, et la route /commande est gardée : sans livre, inutile d'y entrer.


{
  path: "/commande",
  name: "commande",
  component: () => import("./pages/PageCommande.vue"),
  meta: { titre: "Passer la commande" },
  // Pas de commande sans livre : retour au panier, qui explique pourquoi.
  beforeEnter: () => {
    if (usePanierStore().estVide) return { name: "panier", query: { raison: "vide" } };
  },
},

La page du panier explique le renvoi quand la requête contient raison=vide, et n'affiche le lien vers la commande que si le panier contient un livre :


<template>
  <section aria-labelledby="titre-panier">
    <h1
      id="titre-panier"
      tabindex="-1"
    >
      Votre panier
    </h1>
    <p
      v-if="route.query.raison === 'vide' && panier.estVide"
      class="alerte"
    >
      Pour commander, ajoutez d’abord un livre à votre panier.
    </p>
    <PanierContenu />
    <p v-if="!panier.estVide">
      <RouterLink
        class="bouton"
        :to="{ name: 'commande' }"
      >
        Passer la commande
      </RouterLink>
    </p>
    <p>
      <RouterLink :to="{ name: 'catalogue' }">
        Continuer mes achats
      </RouterLink>
    </p>
  </section>
</template>

12.3.2 Envoyer : le composable useEnvoi

L'envoi est le pendant de useFetch : il expose l'état de la requête (en cours, erreurs, réponse) et une fonction qui la lance.


import { ref } from "vue";

/**
 * Envoie du JSON en POST et expose l'état de l'envoi.
 * Le serveur de la Librairie répond 201 + { numero, total },
 * ou 400 + { erreurs: [ … ] }, ou 500 + { erreur }.
 */
export function useEnvoi(url) {
  const envoi = ref(false);
  const erreurs = ref([]);
  const reponse = ref(null);

  async function envoyer(corps) {
    envoi.value = true;
    erreurs.value = [];
    reponse.value = null;
    try {
      const r = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(corps),
      });
      const donnees = await r.json().catch(() => ({})); // corps vide ou illisible
      if (r.ok) reponse.value = donnees;
      else erreurs.value = donnees.erreurs ?? [donnees.erreur ?? `Le serveur a répondu ${r.status}.`];
    } catch {
      erreurs.value = ["Le serveur ne répond pas. Vérifiez votre connexion, puis réessayez."];
    } finally {
      envoi.value = false;
    }
    return reponse.value !== null;
  }

  return { envoi, erreurs, reponse, envoyer };
}

Toutes les issues sont ramenées à une seule forme, une liste de messages : les erreurs détaillées d'un refus (400), le message d'une panne (500), un message par défaut pour un code inattendu, et un message pour un serveur injoignable (fetch rejette sa promesse). La page n'a plus qu'à afficher la liste.

La suite du cours est sur Adloun

Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.

Lire la suite du cours

En accès libre, sans inscription.

Exercices corrigés de ce chapitre

Lire la suite du cours →