Skip to content

Plan d'enseignement - Cours 122

Vue d'ensemble des 15 séances

SéanceDateModuleContenuExercices Nuxy
121.01M1Bases JavaScript1.1 - 1.9
228.01M2Conditions2.1 - 2.6
304.02M3Boucles3.1 - 3.4
411.02M4Fonctions4.1 - 4.4
525.02M5Tableaux (bases)5.1 - 6.10
604.03M1-M7Démo CinéJS + Intro DOM7.1 - 7.7
711.03M7DOMTerminer M7
825.03ProjetPrésentation projet + validation ressource8.1 - 8.4
901.04ProjetCopilot + dépôt + données + premier affichage8.5 - 8.8
1022.04ProjetAffichage dynamique (cartes) + CSSRattrapage
1129.04ProjetTri et rechercheRattrapage
1206.05ProjetAjout et suppressionTout terminé
1313.05ProjetFinalisation et responsive design-
1420.05-Révisions théoriques-
1527.05-EXAMEN BLANC-

Semaines sans cours

  • 18 février (vacances)
  • 18 mars (pas de cours)
  • 8 et 15 avril (vacances de Pâques)

Séance 1 - 21 janvier 2026 ✓

Thème : Bases JavaScript

Objectifs :

  • Comprendre ce qu'est JavaScript et son rôle
  • Savoir utiliser la console du navigateur (DevTools)
  • Déclarer des variables avec let et const
  • Connaître les types de données de base
  • Effectuer des opérations et manipuler du texte
  • Utiliser les méthodes console

Ressources :

Devoirs Nuxy : Exercices 1.1 à 1.9 (tout le module 1)

Séance 2 - 28 janvier 2026 ✓

Thème : Conditions

Objectifs :

  • Écrire des conditions avec if, else if, else
  • Utiliser les opérateurs de comparaison (===, !==, <, >)
  • Combiner des conditions avec && et ||
  • Gérer des cas multiples avec switch/case
  • Utiliser l'opérateur ternaire

Ressources :

Devoirs Nuxy : Exercices 2.1 à 2.6

Séance 3 - 4 février 2026 ✓

Thème : Boucles

Objectifs :

  • Kahoot "JavaScript les bases" - Lien pour s'entraîner seul
  • Répéter des actions avec while
  • Parcourir avec for et for...of
  • Comprendre do...while et break

Ressources :

Devoirs Nuxy : Exercices 3.1 à 3.4

Séance 4 - 11 février 2026 ✓

Thème : Fonctions

Objectifs :

  • Déclarer et appeler des fonctions
  • Passer des paramètres
  • Retourner des valeurs avec return
  • Utiliser la syntaxe arrow function

Ressources :

Devoirs Nuxy : Exercices 4.1 à 4.4

Séance 5 - 25 février 2026 ✓

Thème : Tableaux - Les bases

Objectifs :

  • Créer et manipuler des tableaux
  • Accéder aux éléments par index
  • Modifier un tableau (push, pop, shift, unshift)
  • Parcourir avec forEach
  • Trier avec sort() et filtrer avec filter()

Ressources :

Devoirs Nuxy : Terminer les modules 5 et 6 complets (exercices 5.1 à 5.11, 6.1 à 6.10)

Important

Ces méthodes (filter, map, sort, reduce) seront réutilisées dans le projet final ! Les modules 5 et 6 doivent être terminés pour la séance 6.

Séance 6 - 4 mars 2026 ✓

Thème : Démo CinéJS - Synthèse M1-M6 + Introduction au DOM

Récupérer le projet CinéJS

Étape 1 — Créer votre copie du projet

  1. Ouvrir le dépôt template : github.com/fallinov/esig-122-demo-films
  2. Cliquer sur le bouton vert "Use this template""Create a new repository"

Bouton "Use this template" sur GitHub

  1. Cocher "Include all branches" (pour avoir la branche solution)
  2. Repository name : esig-122-demo-films
  3. Visibilité : Public
  4. Cliquer sur "Create repository"

Formulaire de création avec "Include all branches" coché

Étape 2 — Cloner et ouvrir dans WebStorm

  1. Copier l'URL de votre dépôt : https://github.com/VOTRE-PSEUDO/esig-122-demo-films.git

Bouton "Code" pour copier l'URL de clonage

  1. Dans WebStorm : FileNewProject from Version Control...
  2. Coller l'URL et cliquer sur Clone
  3. Ouvrir index.html dans le navigateur avec Live Edit (icône navigateur en haut à droite)

En classe

Développement live de l'app CinéJS (gestionnaire de films) — phases 1 à 4 (console uniquement) :

  • Phase 1 — Données : tableau d'objets films avec 16 films
  • Phase 2 — Affichage console : afficherFilmsConsole() avec for...of, if/else, template literals
  • Phase 3 — Tri : trierParNote() avec spread [...] et sort()
  • Phase 4 — Recherche : rechercherFilm() avec filter(), includes(), toLowerCase()

Les phases 5-6 (affichage HTML et contrôles interactifs) seront traitées à la séance 7.

Code de la démo : branche sfa-demo-4.3

Concepts révisés

ModuleConcepts
M1Variables, const, template literals
M2Conditions, if/else
M3Boucles for...of
M4Fonctions, paramètres, return
M5filter, sort, spread [...], includes, toLowerCase
M6Tableau d'objets, accès aux propriétés (notation point)

Devoirs : Rattraper Nuxy jusqu'au module 6 inclus pour ceux qui sont en retard

Séance 7 - 11 mars 2026 ✓

Thème : CinéJS (suite) + DOM

Exercices Nuxy (~30 min)

Temps en autonomie pour avancer sur les exercices du module 7 (DOM) :

CinéJS — Phases 5-6 (~45 min)

Suite de la démo séance 6 : on passe de la console à la page HTML.

  • Phase 5 — Affichage HTML : creerCarteFilm() et afficherFilmsHTML() avec querySelector, innerHTML, forEach
  • Phase 6 — Contrôles interactifs : rafraichir() avec addEventListener("input", ...), chaînage recherche → tri → affichage

Concepts DOM introduits

ConceptDescription
querySelector()Sélectionner un élément par son id ou sélecteur CSS
innerHTMLInjecter du HTML généré avec des template literals
addEventListenerRéagir aux actions de l'utilisateur (frappe clavier)
.valueLire la valeur d'un champ <input>

Ressources :

Exercice facultatif : PokéCount

Premier vrai projet dans WebStorm ! Une app de compteur de Pokémon qui met en pratique le DOM dans un projet complet avec HTML, CSS et JavaScript.

Séance 8 - 25 mars 2026 ✓

Thème : Présentation du projet personnel JavaScript

Contenu (~1h30) :

  • Présentation du projet : site web JS natif, affichage dynamique, tri, recherche, ajout, suppression
  • Barème : 20 pts projet + 20 pts examen écrit
  • 👉 Consignes et barème du projet
  • Choix du thème de données (films, recettes, jeux, animaux, etc.)
  • Travail autonome : réfléchir à sa ressource, rattrapage Nuxy

Calendrier du projet :

DateSéanceÉtape projetDevoirs Nuxy
25 mars8Présentation projetTerminer M7 (DOM)
1er avril9Copilot + dépôt + données + premier affichageM8 : 8.1 à 8.4 (événements)
22 avril10Affichage dynamique (cartes)M8 : 8.5 à 8.8 (formulaires)
29 avril11Tri et rechercheRattrapage si retard
6 mai12Ajout et suppression (formulaire)Rattrapage si retard
13 mai13Finalisation et responsiveTout terminé
20 mai14Révisions théoriques
27 mai15Examen blanc
7 juin (23h59)Rendu final du projet
9 juinExamen de module (30 min)

Devoirs Nuxy obligatoires

Les exercices Nuxy couvrent la théorie nécessaire au projet. Chaque semaine, vous devez terminer le module indiqué avant la séance suivante (~1h de travail à la maison).

Si les exercices ne sont pas faits, vous serez bloqué en classe sur votre projet.

Modules requis pour le projet :

  • M7 (DOM) → affichage dynamique
  • M8 (événements, formulaires) → tri, recherche, ajout, suppression

Ressources :

Séance 9 - 1er avril 2026 ✓

Thème : Mise en place du projet — Dépôt, Copilot, données et premier affichage

Prérequis Nuxy : M7 (DOM) terminé

Rappel Git (~5 min)

Rappel rapide du workflow Git pour le projet :

  • Branches fix/ (corriger un bug) et feat/ (nouvelle fonctionnalité)
  • Fusionner puis supprimer la branche
  • Pour ce projet solo : travailler directement sur main est toléré
  • Toujours faire Commit + Push (= backup sur GitHub)

Étape 1 — Créer le dépôt depuis le template (~5 min)

  1. Ouvrir le dépôt template : github.com/fallinov/esig-122-projet-template
  2. Cliquer sur le bouton vert "Use this template""Create a new repository"
  3. Repository name : 122-projet-perso-prenom-nom (ex : 122-projet-perso-steve-fallet)
  4. Visibilité : Public (ou privé, au choix)
  5. Cliquer sur "Create repository"

Étape 2 — Publier sur GitHub Pages (~5 min)

  1. Sur GitHub → Settings (du dépôt) → Pages (menu gauche)
  2. Source : Deploy from a branch
  3. Branch : main / dossier / (root)
  4. Cliquer Save
  5. Vérifier dans Actions que le build est terminé (coche verte)

Vérifier la publication

Après chaque git push, GitHub Pages reconstruit automatiquement (~1-2 min). Vérifier dans l'onglet Actions que tout est au vert.

Étape 3 — Cloner dans WebStorm (~5 min)

  1. Copier l'URL de votre dépôt (pas celle du site GitHub Pages !)
  2. Dans WebStorm : FileNewProject from Version Control...
  3. Coller l'URL et cliquer sur Clone

Le template contient la structure exigée par les consignes du projet :

mon-projet/
├── index.html       ← Page principale
├── css/
│   └── style.css    ← Styles
├── js/
│   └── script.js    ← JavaScript
├── img/             ← Images (vide pour l'instant)
├── .jshintrc        ← Règles qualité JS
├── .gitignore
└── README.md

Étape 4 — Envoyer les liens au formateur (~2 min)

Envoyer un message Teams au formateur avec :

  • Le lien de votre dépôt GitHub (code source)
  • Le lien de votre GitHub Pages (site en ligne)

Étape 5 — Installer GitHub Copilot dans WebStorm (~10 min)

  1. SettingsPlugins → onglet Marketplace → rechercher "GitHub Copilot" → Install
  2. Redémarrer WebStorm
  3. Se connecter à GitHub quand Copilot le demande (icône en bas de l'IDE)

GitHub Copilot gratuit pour les étudiants

Copilot est gratuit avec le GitHub Student Developer Pack. Si vous n'avez pas encore activé votre pack étudiant, faites-le dès que possible.

Modes de Copilot :

ModeUsage
AskPoser une question, obtenir une explication
AgentLui donner une tâche, il modifie les fichiers
PlanPlanifier un développement en plusieurs étapes

Choisir le bon modèle

  • Claude Haiku : tâches simples (génération de données, questions factuelles) — économe en tokens
  • Modèles premium (Opus, GPT-4o) : raisonnement complexe — consomme beaucoup plus de tokens

En mode gratuit, vos tokens sont limités. Utilisez Haiku pour les petites tâches !

Étape 6 — Configurer Copilot pour le projet (~5 min)

Créer le fichier .github/copilot-instructions.md à la racine du dépôt :

md
# Instructions pour GitHub Copilot

## Contexte du Projet
- Projet pédagogique pour apprentis en informatique
- Site web de gestion de collection en JavaScript
- Public cible : étudiants (niveau débutant-intermédiaire)

## Rôle de l'IA
L'IA ne doit **pas** coder à la place de l'apprenti. Son rôle est de :
- **Expliquer** les concepts et la syntaxe
- **Guider** vers la solution (poser des questions)
- **Revoir** le code écrit par l'apprenti

## Langue
- Messages de commit : français
- Code (variables, fonctions) : anglais

Ce fichier est lu automatiquement par Copilot à chaque question.

Étape 7 — Générer les données avec Copilot (~15 min)

Dans js/script.js, utilisez GitHub Copilot (mode Agent) pour générer votre tableau de données.

Avant de prompter, vous devez :

  1. Écrire vous-même un objet exemple avec les propriétés que vous voulez
  2. Faire valider votre structure par l'enseignant
  3. Utiliser le prompt ci-dessous dans le chat Copilot (ou une autre IA) en remplaçant les [...]

Prompt à copier dans Copilot / une IA

text
Je suis étudiant en informatique et je crée un site web en JavaScript
pour gérer une collection de [TA RESSOURCE, ex: jeux vidéo].

Voici un exemple de ce que je veux obtenir, avec UN objet :

const data = [
  {
    id: 1,
    name: "The Witcher 3",
    category: "RPG",
    platform: "PC",
    rating: 9.5,
    year: 2015,
    image: "https://placehold.co/400x300/4a90d9/white?text=The+Witcher+3"
  }
];

Génère un tableau de 10 objets avec EXACTEMENT les mêmes propriétés
que mon exemple. Les données doivent être réalistes et variées :
- Au moins 3-4 valeurs différentes pour [PROPRIÉTÉ CATÉGORIE, ex: category]
- Des valeurs numériques variées pour [PROPRIÉTÉ TRIABLE, ex: rating]
- Les id de 1 à 10
- Pour chaque image, utilise https://placehold.co/400x300/COULEUR/white?text=NOM
  avec une couleur hex différente par catégorie et le nom de l'élément
  (espaces remplacés par des +)

Remplace le contenu du tableau data par ces nouvelles données.
Donne-moi UNIQUEMENT le code JavaScript, rien d'autre.
Exemple de résultat attendu
js
const data = [
  {
    id: 1,
    name: "The Witcher 3",
    category: "RPG",
    platform: "PC",
    rating: 9.5,
    year: 2015,
    image: "https://placehold.co/400x300/4a90d9/white?text=The+Witcher+3"
  },
  {
    id: 2,
    name: "FIFA 25",
    category: "Sport",
    platform: "PS5",
    rating: 7.2,
    year: 2024,
    image: "https://placehold.co/400x300/2ecc71/white?text=FIFA+25"
  },
  // ... 8 autres objets
];

Les images placeholder affichent le nom sur un fond coloré par catégorie. Tu les remplaceras plus tard par de vraies images dans un dossier img/.

Étape 8 — Afficher les données dans la page (~15 min)

Afficher les données sous forme de liste <ul> simple dans la page.

Objectif : pour chaque élément du tableau, créer un <li> avec le nom et l'image.

Indice — avec forEach
js
// Récupère la liste
const ulList = document.getElementById("list");

// Parcours le tableau et crée un <li> par élément
data.forEach(item => {
  ulList.innerHTML += `
    <li>
      <div>${item.name}</div>
      <div><img src="${item.image}" alt="${item.name}"></div>
    </li>`;
});

Une fois l'affichage fonctionnel :

  1. Vérifier que la page fonctionne en local (ouvrir index.html dans le navigateur)
  2. Commit + Push → vérifier sur GitHub Pages que le site est à jour
  3. Vérifier qu'il n'y a aucune erreur dans la console du navigateur (F12)

Code de la démo : fallinov/122-projet-perso-steve-fallet

Devoirs pour la séance 10

Nuxy : Exercices 8.1 à 8.4 (événements)

Projet : Avoir la structure HTML/CSS de base + le tableau d'objets dans js/script.js + affichage <ul> fonctionnel sur GitHub Pages. Commencer à styliser l'affichage (CSS, cartes, tableau...).

Ressources :

Séance 10 - 22 avril 2026 ✓

Thème : Projet — Affichage dynamique avec fonction et cartes CSS

Prérequis Nuxy : M8 exercices 8.1 à 8.4 (événements)

Prérequis projet : Tableau de données + affichage basique fonctionnel (séance 9)

Objectifs

  • Encapsuler l'affichage dans une fonction réutilisable
  • Remplacer la liste basique par des cartes HTML/CSS
  • Comprendre pourquoi une fonction d'affichage est nécessaire (tri, recherche, ajout necessitent de rafraîchir)

Étape 1 — Créer la fonction displayItems() (~20 min)

Transformer le code de la séance 9 en une fonction réutilisable :

js
/**
 * Affiche les éléments dans la page
 * @param {Array} items - Tableau d'objets à afficher
 */
function displayItems(items) {
  const container = document.getElementById("list");
  let html = "";

  items.forEach(item => {
    html += `<article class="card">...</article>`;
  });

  container.innerHTML = html;
}

// Appel au chargement de la page
displayItems(data);

Pourquoi une fonction ?

Plus tard, vous aurez besoin de rafraîchir l'affichage après un tri, une recherche ou un ajout. Avoir une fonction permet de la rappeler à chaque modification :

js
// Après un tri
const sorted = [...data].sort((a, b) => b.rating - a.rating);
displayItems(sorted);

Étape 2 — Créer le HTML d'une carte (~20 min)

Remplacer le simple <li> par une carte plus riche avec image, titre et infos :

Exemple de structure HTML pour une carte
html
<article class="card">
  <img src="..." alt="...">
  <div class="card-body">
    <h2>Nom de l'élément</h2>
    <p>Catégorie — Année</p>
    <span class="rating">9.5</span>
  </div>
</article>

Adapter la structure à votre thème (films, recettes, animaux...).

Étape 3 — Styliser les cartes en CSS (~30 min)

Mettre en forme les cartes avec CSS :

  • Grille responsive (display: grid ou flexbox avec flex-wrap)
  • Carte avec ombre, bordure arrondie, hover
  • Image en haut, contenu en bas
  • Responsive : 1 colonne mobile, 2-3 colonnes desktop
Exemple CSS minimal
css
#list {
  display: grid;
  /* repeat(auto-fill, minmax(280px, 1fr)) :
     - auto-fill : crée autant de colonnes que possible
     - minmax(280px, 1fr) : chaque colonne fait min 280px, max 1 fraction de l'espace */
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  /* gap : espace entre chaque cellule de la grille */
  gap: 1.5rem;
  /* Supprime les puces d'une liste <ul> */
  list-style: none;
  padding: 0;
}

.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  /* Masque ce qui dépasse du conteneur — indispensable pour que border-radius
     s'applique aussi à l'image (sinon ses coins dépassent de la carte) */
  overflow: hidden;
  /* Anime les changements de la propriété transform sur 0.2 secondes */
  transition: transform 0.2s;
}

.card:hover {
  /* translateY(-4px) : déplace la carte de 4px vers le haut au survol */
  transform: translateY(-4px);
  /* box-shadow : décalage-x  décalage-y  flou  couleur
     rgba(0, 0, 0, 0.1) = noir à 10% d'opacité → ombre subtile */
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.card img {
  width: 100%;
  height: 200px;
  /* cover : remplit tout l'espace en gardant les proportions, coupe les bords si besoin
     (sans ça, l'image serait déformée ou laisserait des espaces vides) */
  object-fit: cover;
}

.card-body {
  padding: 1rem;
}

Étape 4 — Commit + Push + Vérifier (~5 min)

  1. Vérifier le rendu en local et sur mobile (DevTools → responsive)
  2. Console propre (F12, pas d'erreurs)
  3. Commit + Push avec un message clair (ex : feat: affichage en cartes avec CSS)
  4. Vérifier sur GitHub Pages

Devoirs pour la séance 11

Nuxy : Exercices 8.5 à 8.8 (formulaires)

Projet : L'affichage dynamique avec cartes doit être fonctionnel et déployé sur GitHub Pages

Ressources :

Séance 11 - 29 avril 2026 ✓

Thème : Projet — Tri, recherche et bonnes pratiques d'affichage

Prérequis Nuxy : M8 terminé (événements + formulaires)

Prérequis projet : Affichage en cartes fonctionnel (séance 10)

Objectifs

  • Comprendre pourquoi innerHTML += dans une boucle est une mauvaise pratique
  • Refactoriser la fonction d'affichage pour qu'elle soit performante
  • Ajouter un bouton de tri sur la collection
  • Ajouter un champ de recherche
  • Chaîner recherche + tri pour afficher le résultat correct

Étape 1 — Corriger la fonction d'affichage (~15 min)

À la séance 10, vous avez probablement écrit votre fonction d'affichage comme ceci :

js
// ❌ Pattern à éviter
function displayItems(items) {
  const container = document.getElementById("list");
  container.innerHTML = "";
  items.forEach(item => {
    container.innerHTML += `<article class="card">...</article>`;
  });
}

Pourquoi c'est un problème ?

Chaque fois que vous écrivez dans innerHTML, le navigateur :

  1. Lit tout le HTML existant dans le conteneur
  2. Le concatène avec votre nouveau morceau
  3. Reconstruit entièrement le DOM du conteneur

Avec 10 cartes, le navigateur reconstruit le DOM 10 fois de suite. Pour 1000 cartes, ce serait dramatique. C'est ce qu'on appelle un anti-pattern : ça marche, mais c'est mauvais.

La correction : accumuler puis écrire une seule fois

js
// ✅ Pattern recommandé
function displayItems(items) {
  const container = document.getElementById("list");
  let html = "";
  items.forEach(item => {
    html += `
      <article class="card" data-id="${item.id}">
        <img src="${item.image}" alt="${item.name}">
        <div class="card-body">
          <h2>${item.name}</h2>
          <p>${item.category} — ${item.year}</p>
          <span class="rating">${item.rating}</span>
        </div>
      </article>
    `;
  });
  container.innerHTML = html;  // Une seule écriture DOM !
}

Deux changements clés :

  1. La variable html accumule le texte, on écrit dans le DOM une seule fois à la fin.
  2. L'attribut data-id="${item.id}" permet d'identifier chaque carte (utile en séance 12 pour la suppression).

Étape 2 — Bouton de tri qui inverse l'ordre (~25 min)

Ajouter un bouton qui trie par note. À chaque clic, l'ordre s'inverse (croissant ↔ décroissant) et le texte du bouton est mis à jour.

HTML (au-dessus de la liste) :

html
<button id="btn-sort">Trier par note ↓</button>

JavaScript :

js
const btnSort = document.getElementById("btn-sort");

// État du tri : true = croissant, false = décroissant
let sortAsc = false;

btnSort.addEventListener("click", () => {
  sortAsc = !sortAsc;  // inverser l'état à chaque clic

  const sorted = [...data].sort((a, b) =>
    sortAsc ? a.rating - b.rating : b.rating - a.rating
  );

  // Mettre à jour le texte du bouton selon le sens du tri
  btnSort.textContent = sortAsc ? "Trier par note ↑" : "Trier par note ↓";

  displayItems(sorted);
});

Concepts introduits :

  • addEventListener("click", ...) : réagir à un clic
  • État : une variable (sortAsc) mémorise la situation actuelle entre deux clics
  • !sortAsc : opérateur NON logique pour inverser un booléen
  • [...data] : copier le tableau pour ne pas modifier l'original
  • Array.sort((a, b) => ...) avec ternaire : choisir le sens du tri à la volée
  • .textContent : modifier le texte affiché du bouton

Pourquoi copier le tableau avec [...data] ?

sort() modifie le tableau d'origine. Sans la copie, vous perdriez l'ordre initial après le premier clic. Avec [...data], le tableau data reste intact, et chaque clic retrie depuis l'ordre de départ.

Comprendre a.rating - b.rating vs b.rating - a.rating

  • Si la soustraction est positive, sort() place a après b.
  • Si elle est négative, a est placé avant b.

Donc a.rating - b.rating trie du plus petit au plus grand (croissant), et b.rating - a.rating du plus grand au plus petit (décroissant). C'est pour ça qu'inverser les variables suffit à inverser l'ordre.

Étape 3 — Champ de recherche (~25 min)

Ajouter un champ qui filtre les jeux selon le nom saisi.

HTML :

html
<input type="text" id="search" placeholder="Rechercher un jeu...">

JavaScript :

js
const searchInput = document.getElementById("search");

searchInput.addEventListener("input", () => {
  const query = searchInput.value.toLowerCase();
  const filtered = data.filter(item =>
    item.name.toLowerCase().includes(query)
  );
  displayItems(filtered);
});

Concepts introduits :

  • addEventListener("input", ...) : réagir à chaque frappe au clavier
  • .value : lire le contenu actuel du champ
  • Array.filter() : garder seulement les éléments qui correspondent
  • String.includes() + toLowerCase() : recherche insensible à la casse

Regrouper les deux éléments dans un <div class="toolbar"> dans index.html, juste au-dessus de <ul id="list"> :

html
<div class="toolbar">
  <input type="text" id="search" placeholder="Rechercher un jeu...">
  <button id="btn-sort">Trier par note ↓</button>
</div>
<ul id="list"></ul>

Et le CSS correspondant dans style.css :

css
.toolbar {
  display: flex;
  gap: 1rem;
  /* align-items: center : aligne verticalement le champ et le bouton */
  align-items: center;
  margin-bottom: 1.5rem;
}

#search {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 1rem;
  /* flex: 1 : le champ prend tout l'espace disponible dans le flex */
  flex: 1;
  /* max-width : limite la largeur sur grand écran */
  max-width: 400px;
}

#search:focus {
  /* Supprime le contour par défaut, remplacé par la bordure colorée */
  outline: none;
  border-color: #4a90d9;
}

Étape 4 — Chaîner tri + recherche (~10 min)

Si on déclenche tri puis recherche, le second appel écrase le premier. Pour combiner, on regroupe la logique dans une seule fonction refresh() appelée à chaque interaction. Elle filtre d'abord, puis trie selon l'état sortAsc.

js
function refresh() {
  const query = searchInput.value.toLowerCase();

  // 1. Filtrer selon le champ de recherche
  let result = data.filter(item =>
    item.name.toLowerCase().includes(query)
  );

  // 2. Trier selon l'état du bouton
  result = [...result].sort((a, b) =>
    sortAsc ? a.rating - b.rating : b.rating - a.rating
  );

  // 3. Afficher
  displayItems(result);
}

// Recherche : à chaque frappe, on rafraîchit
searchInput.addEventListener("input", refresh);

// Tri : on inverse l'état, on met à jour le bouton, puis on rafraîchit
btnSort.addEventListener("click", () => {
  sortAsc = !sortAsc;
  btnSort.textContent = sortAsc ? "Trier par note ↑" : "Trier par note ↓";
  refresh();
});

Pourquoi refresh() plutôt que deux fonctions séparées ?

Si la recherche faisait son propre affichage de son côté et le tri aussi, ils s'écraseraient mutuellement (taper dans la recherche annulerait le tri en cours). En centralisant dans refresh(), on combine systématiquement filtre + tri avant chaque affichage.

Étape 5 — Commit + Push + Vérifier (~5 min)

  1. Tester le tri et la recherche dans le navigateur
  2. Vérifier la console (F12) : aucune erreur
  3. Commit + Push avec un message clair (ex : feat: tri et recherche)
  4. Vérifier sur GitHub Pages que tout fonctionne en ligne

⚠️ À retenir pour la séance 12 — la délégation d'événements

Aujourd'hui, vos écouteurs sont sur <input> et <button>, qui restent dans la page : pas de souci. Mais en séance 12, vous mettrez un bouton Supprimer sur chaque carte.

Problème : à chaque appel de displayItems(), le navigateur reconstruit toutes les cartes. Les listeners attachés directement aux cartes disparaissent :

js
// ❌ Ne fonctionnera plus après un tri ou une recherche
document.querySelectorAll(".card").forEach(card => {
  card.addEventListener("click", () => alert("clic !"));
});

Solution : la délégation d'événements. Un seul listener sur le parent stable (#list), qui détecte sur quelle carte on a cliqué grâce à event.target.closest(".card") :

js
// ✅ Un listener pour toutes les cartes, présentes ET futures
document.getElementById("list").addEventListener("click", (event) => {
  const card = event.target.closest(".card");
  if (!card) return;
  console.log("Carte cliquée, id :", card.dataset.id);
});

C'est pour ça qu'on a ajouté data-id à chaque carte en étape 1. On l'utilisera dès la semaine prochaine.

Devoirs pour la séance 12

Projet : Le tri et la recherche doivent être fonctionnels et déployés sur GitHub Pages.

Ressources :

Séance 12 - 6 mai 2026 ✓

Thème : Projet — Ajout et suppression

Prérequis Nuxy : M8 terminé (formulaires)

Prérequis projet : Tri et recherche fonctionnels et déployés (séance 11)

Objectifs

  • Comprendre la structure d'un formulaire HTML et intercepter sa soumission
  • Ajouter une ressource au tableau avec push() et rafraîchir l'affichage
  • Utiliser la délégation d'événements (préparée séance 11) pour les boutons Supprimer
  • Supprimer un élément avec filter() sans muter le tableau d'origine

Étape 1 — Créer le formulaire HTML (~20 min)

Ajouter le formulaire dans index.html, au-dessus ou à côté de la liste :

html
<form id="form-add">
  <h2>Ajouter</h2>

  <div class="form-group">
    <label for="input-name">Nom</label>
    <input type="text" id="input-name" placeholder="Ex : The Witcher 3" required>
  </div>

  <div class="form-group">
    <label for="input-category">Catégorie</label>
    <select id="input-category">
      <option value="RPG">RPG</option>
      <option value="Action">Action</option>
      <option value="Stratégie">Stratégie</option>
    </select>
  </div>

  <div class="form-group">
    <label for="input-rating">Note (1–10)</label>
    <input type="number" id="input-rating" min="1" max="10" placeholder="8">
  </div>

  <button type="submit">Ajouter</button>
</form>

CSS pour le formulaire :

css
.form-group {
  display: flex;
  /* column : empile les enfants verticalement (label AU-DESSUS du champ) */
  flex-direction: column;
  /* gap : espace entre les enfants flex (ici entre le label et le champ) */
  gap: 4px;
  margin-bottom: 12px;
}

/* Cibler uniquement les champs du formulaire, pas tous les inputs de la page */
.form-group input,
.form-group select {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  /* Les <input> et <select> n'héritent pas automatiquement de la police du body */
  font-size: 1rem;
}

.form-group input:focus,
.form-group select:focus {
  /* Supprime le contour bleu/orange par défaut du navigateur au focus */
  outline: none;
  /* On le remplace par notre propre couleur de bordure */
  border-color: #0078d4;
}

/* [type="submit"] : sélecteur d'attribut — cible uniquement les boutons de soumission */
form button[type="submit"] {
  /* padding: vertical horizontal (10px haut/bas, 20px gauche/droite) */
  padding: 10px 20px;
  background-color: #0078d4;
  color: white;
  /* Les <button> ont une bordure par défaut du navigateur — on la supprime */
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  /* Affiche la main au survol — comportement attendu sur un bouton cliquable */
  cursor: pointer;
}

form button[type="submit"]:hover {
  /* Couleur plus foncée au survol : feedback visuel que le bouton est cliquable */
  background-color: #005fa3;
}

Points clés :

  • <label for="..."> + id="..." sur l'input : cliquer le label met le focus sur le champ
  • type="number" + min/max : validation native du navigateur
  • required : le navigateur refuse la soumission si le champ est vide
  • type="submit" déclenche l'événement submit sur le <form> parent

Étape 2 — Intercepter la soumission et ajouter au tableau (~25 min)

js
const form = document.getElementById("form-add");
const inputName = document.getElementById("input-name");
const inputCategory = document.getElementById("input-category");
const inputRating = document.getElementById("input-rating");

form.addEventListener("submit", (event) => {
  event.preventDefault(); // Empêcher le rechargement de la page

  const newItem = {
    id: Date.now(),                    // ID unique basé sur l'horodatage
    name: inputName.value.trim(),      // trim() supprime les espaces en début/fin
    category: inputCategory.value,
    rating: Number(inputRating.value), // .value est toujours une string → convertir
    // Générer une image placeholder avec le nom de l'élément
    image: `https://placehold.co/400x300/7f8c8d/white?text=${encodeURIComponent(inputName.value.trim())}`
  };

  data.push(newItem); // Ajouter au tableau
  refresh();          // Rafraîchir l'affichage

  form.reset();       // Vider tous les champs en une ligne
});

Concepts introduits :

  • event.preventDefault() : empêcher le comportement par défaut du <form> (rechargement ou envoi vers un serveur)
  • .value.trim() : lire la valeur et supprimer les espaces superflus
  • Number() : convertir la chaîne en nombre ("8"8) — les .value sont toujours des strings
  • Date.now() : horodatage en millisecondes, unique à chaque soumission → bon ID temporaire
  • form.reset() : réinitialiser tous les champs du formulaire en une seule instruction

Pourquoi Date.now() comme ID ?

En production, les IDs viennent d'une base de données (1, 2, 3…). En JS pur, sans serveur, Date.now() donne un entier unique à chaque milliseconde — suffisant pour un projet personnel où on n'ajoute jamais deux éléments en même temps.

Étape 3 — Ajouter le bouton Supprimer sur chaque carte (~15 min)

Depuis la séance 11, displayItems() génère data-id="${item.id}" sur chaque carte. On y ajoute maintenant le bouton :

js
function displayItems(items) {
  const container = document.getElementById("list");
  let html = "";
  items.forEach(item => {
    html += `
      <article class="card" data-id="${item.id}">
        <img src="${item.image}" alt="${item.name}">
        <div class="card-body">
          <h2>${item.name}</h2>
          <p>${item.category} — ${item.year}</p>
          <span class="rating">${item.rating} ⭐</span>
          <button class="btn-delete">Supprimer</button>
        </div>
      </article>
    `;
  });
  container.innerHTML = html;
}

Ajouter dans style.css le style du bouton :

css
.btn-delete {
  margin-top: 8px;
  padding: 6px 12px;
  background-color: #e74c3c;
  color: white;
  /* Supprime la bordure par défaut des <button> */
  border: none;
  border-radius: 4px;
  /* Affiche la main au survol */
  cursor: pointer;
  /* Légèrement plus petit que le texte normal (1rem) */
  font-size: 0.85rem;
}

.btn-delete:hover {
  /* Rouge plus foncé au survol — feedback visuel */
  background-color: #c0392b;
}

Étape 4 — Supprimer avec la délégation d'événements (~20 min)

Avant de commencer : passer data de const à let

La suppression va remplacer le tableau par une version filtrée :

js
data = data.filter(/* … */);

Cette réassignation lève TypeError: Assignment to constant variable si data est déclaré avec const (depuis la séance 9).

Avant de coder la suppression, modifier la déclaration en haut de js/script.js :

js
// Avant (séances 9 à 11)
const data = [/* … */];

// Après (séance 12, pour permettre la suppression)
let data = [/* … */];

Pourquoi ce changement maintenant ?

Jusqu'ici on n'a fait que muter le tableau (data.push(...), data.sort(...)) — const l'autorise car la référence ne change pas. La suppression est différente : on recrée un nouveau tableau et on l'assigne à data — ça, const l'interdit.

Rappel de la séance 11 : les boutons sont recréés à chaque displayItems(), donc un listener direct dessus disparaîtrait. On délègue au conteneur parent stable (#list) :

js
document.getElementById("list").addEventListener("click", (event) => {
  // Vérifier si le clic vient d'un bouton Supprimer
  const btn = event.target.closest(".btn-delete");
  if (!btn) return; // Clic ailleurs sur la carte → ignorer

  // Remonter à la carte pour récupérer l'id
  const card = btn.closest(".card");
  const id = Number(card.dataset.id); // dataset.id est une string → convertir

  // Confirmation avant suppression
  if (!confirm("Supprimer cet élément ?")) return;

  // Recréer le tableau sans l'élément supprimé
  data = data.filter(item => item.id !== id);
  refresh();
});

Concepts introduits :

  • event.target.closest(".btn-delete") : remonter dans le DOM jusqu'à trouver l'ancêtre correspondant (ou null)
  • card.dataset.id : lire l'attribut data-id de la carte
  • Number(...) : dataset.id est une string, il faut la convertir pour comparer avec ===
  • confirm() : boîte de dialogue native (retourne true ou false)
  • data = data.filter(...) : remplacer le tableau par une version sans l'élément supprimé

closest() vs event.target

event.target est l'élément exactement cliqué — peut être le texte à l'intérieur du bouton, pas le bouton lui-même. closest(".btn-delete") remonte dans les parents jusqu'à trouver un élément qui correspond au sélecteur. C'est la bonne façon de faire de la délégation.

Étape 5 — Commit + Push + Vérifier (~10 min)

  1. Tester l'ajout : remplir → soumettre → carte apparaît → formulaire vidé
  2. Tester la suppression : clic Supprimer → confirmation → carte disparaît
  3. Tester la combinaison : ajouter, trier, rechercher, puis supprimer → tout reste cohérent
  4. Console (F12) : zéro erreur
  5. Commit + Push (ex : feat: ajout et suppression)
  6. Vérifier sur GitHub Pages que tout fonctionne en ligne

Devoirs pour la séance 13

Projet : L'ajout et la suppression doivent être fonctionnels et déployés sur GitHub Pages.

Ressources :

Séance 13 - 13 mai 2026

Thème : Projet — Finalisation et responsive design

Prérequis projet : Ajout et suppression fonctionnels et déployés (séance 12)

Objectifs

  • Afficher un message "Aucun résultat" quand le filtre ne retourne rien
  • Vérifier et corriger l'affichage sur mobile (responsive)
  • Améliorer la validation du formulaire côté JS
  • Rédiger le README.md du projet
  • Préparer le rendu final sur GitHub Pages

Étape 1 — Message "Aucun résultat" (~20 min)

Quand la recherche ne retourne rien, la liste est vide et l'utilisateur voit une page blanche — impossible de distinguer un bug d'une absence de résultats.

Modifier displayItems() pour gérer ce cas avec une guard clause :

js
function displayItems(items) {
  const container = document.getElementById("list");

  // Guard clause : si le tableau est vide, afficher un message et stopper
  if (items.length === 0) {
    container.innerHTML = '<p class="no-result">Aucun résultat pour cette recherche.</p>';
    return;
  }

  let html = "";
  items.forEach(item => {
    html += `
      <article class="card" data-id="${item.id}">
        <!-- Reprendre ici le code complet de la carte (S12 étape 3) -->
      </article>
    `;
  });
  container.innerHTML = html;
}

Le container doit être un <div>, pas un <ul>

Un <ul> ne doit contenir que des <li>. Si vous insérez un <p class="no-result"> ou des <article> directement, le HTML est invalide.

Dans index.html, vérifiez que vous avez bien :

html
<div id="list"></div>

et non :

html
<ul id="list"></ul>

CSS pour le message :

css
.no-result {
  /* Texte discret — information secondaire, pas une erreur */
  color: #666;
  font-style: italic;
  padding: 1rem 0;
}

Tester : saisir zzz dans le champ de recherche → le message s'affiche. Effacer → les cartes réapparaissent.

Étape 2 — Tester le responsive (~20 min)

La grille CSS repeat(auto-fill, minmax(280px, 1fr)) gère déjà le responsive pour les cartes — elle réduit le nombre de colonnes automatiquement quand l'écran rétrécit.

Ce qui peut encore poser problème : la barre d'outils (champ de recherche + bouton tri) sur petits écrans.

Ouvrir les DevTools (F12) → icône mobile en haut → tester à 375px (iPhone) et 768px (iPad).

Si la toolbar déborde ou se serre, ajouter une media query dans style.css :

css
/* @media : les règles à l'intérieur ne s'appliquent QUE si la largeur
   de la fenêtre est inférieure ou égale à 600px */
@media (max-width: 600px) {
  .toolbar {
    /* Sur mobile : empiler les éléments verticalement */
    flex-direction: column;
  }

  #search {
    /* Supprimer la limite de 400px — utiliser toute la largeur disponible */
    max-width: 100%;
  }
}

Comprendre la media query

@media (max-width: 600px) { } fonctionne comme :hover : ce sont des règles qui surchargent les règles normales, mais conditionnées à la taille de l'écran. En dessous de 600px, flex-direction: column remplace flex-direction: row (ou le défaut).

Étape 3 — Validation du formulaire (~20 min)

Les attributs HTML required et type="number" gèrent la validation de base. Pour bloquer des valeurs invalides comme 0 ou -5, on ajoute une vérification côté JS.

Désactiver la validation HTML5 native — novalidate

Problème : si vous gardez required et min/max sur les inputs, le navigateur affiche ses propres bulles d'erreur et bloque le submit AVANT que votre code JS ne s'exécute. Résultat : vos alert() et .focus() JS ne se déclenchent jamais.

Solution : ajouter l'attribut novalidate sur le <form> pour désactiver la validation HTML5 native. Le JS prend le contrôle complet.

html
<form id="form-add" novalidate>
  <!-- On garde required, min, max comme documentation et fallback,
       mais c'est notre JS qui valide pour de vrai. -->
</form>

Ce code remplace le listener submit de la séance 12. Les vérifications s'ajoutent au début, avant le push().

Réécrire le listener submit :

js
form.addEventListener("submit", (event) => {
  event.preventDefault();

  const name = inputName.value.trim();
  const rating = Number(inputRating.value);

  // Vérification du nom
  if (!name) {
    alert("Le nom est requis.");
    inputName.focus(); // Replace le curseur dans le champ problématique
    return;
  }

  // Vérification de la note
  if (!rating || rating < 1 || rating > 10) {
    alert("La note doit être entre 1 et 10.");
    inputRating.focus();
    return;
  }

  const newItem = {
    id: Date.now(),
    name: name,
    category: inputCategory.value,
    rating: rating,
    image: `https://placehold.co/400x300/7f8c8d/white?text=${encodeURIComponent(name)}`
  };

  data.push(newItem);
  refresh();
  form.reset();
});

Concepts introduits :

  • .focus() : replace le curseur dans le champ invalide → l'utilisateur sait exactement où corriger
  • Validation "défensive" : vérifier avant d'insérer → jamais de données invalides dans data

required ne suffit pas

required empêche la soumission si le champ est vide, mais pas si la valeur est 0 ou -5. La validation JS complète ce que HTML ne peut pas faire.

Étape 4 — Rédiger le README.md (~15 min)

Le README est la carte de visite du dépôt GitHub. Il doit permettre à quelqu'un de comprendre en 30 secondes ce que fait l'application.

Créer ou compléter README.md à la racine du dépôt :

markdown
# [Titre du projet]

Brève description (1-2 phrases) de ce que fait l'application.

## Fonctionnalités

- Affichage de la collection en cartes
- Recherche en temps réel
- Tri par note (ascendant / descendant)
- Ajout d'un élément via formulaire
- Suppression avec confirmation

## Technologies

- HTML5, CSS3
- JavaScript ES6+ (sans framework)

## Démo

[Voir la démo](https://votre-username.github.io/votre-repo/)

Étape bonus — Accessibilité (~10 min, facultatif)

Trois corrections rapides qui améliorent l'accessibilité et la qualité du projet. Ces points peuvent rapporter sur la grille d'évaluation « UX / Qualité du code ».

1. Label invisible sur le champ de recherche

Un placeholder n'est pas un label : il disparaît dès qu'on tape, et les lecteurs d'écran ne l'annoncent pas toujours correctement. Solution : ajouter un <label> visuellement masqué (sr-only).

html
<label for="search" class="sr-only">Rechercher un jeu</label>
<input type="text" id="search" placeholder="Rechercher un jeu...">

Ajouter la classe utilitaire dans style.css :

css
/* Classe "screen-reader only" — masque visuellement mais reste accessible
   aux lecteurs d'écran (lecteurs vocaux pour personnes malvoyantes). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

2. Contraste du bouton Supprimer

Le rouge #e74c3c sur du texte blanc a un ratio de contraste de 3.82 — en dessous du seuil WCAG AA (4.5 pour du texte normal). Foncer la couleur :

css
.btn-delete {
  background-color: #c0392b; /* Ratio 5.8 — conforme WCAG AA */
  /* ... */
}

.btn-delete:hover {
  background-color: #a02b1f; /* Encore plus foncé au survol */
}

3. Vérifier avec DevTools

Onglet Lighthouse (F12 → Lighthouse) → générer un audit « Accessibility ». Viser un score ≥ 90.

Étape 5 — Audit final + commit (~15 min)

Avant le commit final :

  1. Console F12 : zéro erreur et zéro avertissement
  2. Tester tous les cas :
    • Recherche → résultats → message "Aucun résultat"
    • Tri ASC puis DESC
    • Ajout → la nouvelle carte apparaît, formulaire vidé
    • Suppression → confirmation → carte disparaît
    • Formulaire invalide (nom vide, note hors plage) → message d'erreur
  3. Tester sur mobile (DevTools → 375px) : aucun débordement
  4. Commit + Push (ex : feat: finalisation — responsive, validation, README)
  5. Vérifier sur GitHub Pages que la démo en ligne est à jour

Rendu final

Le projet doit être rendu le 7 juin 2026 à 23h59 sur GitHub. Il reste 3 semaines après cette séance pour peaufiner à la maison.

Nuxy

Tous les modules 1 à 8 doivent être terminés pour l'examen du 9 juin. S'il vous reste des exercices en retard, c'est le moment de rattraper.

Code de la démo formateur

La démo fallinov/122-projet-perso-steve-fallet (branche etape-14-start) contient l'état complet de fin de séance 13. Les noms y sont en français (afficherJeux, tabJeux, jeu, nouveauJeu) car le thème est Jeux Vidéo — les concepts sont identiques au plan, seul le nommage diffère selon le thème du projet.

Ressources :

Séance 14 - 20 mai 2026

Thème : Révisions théoriques — Préparation à l'examen

Prérequis : Nuxy modules 1 à 8 terminés

Objectifs

  • Identifier et combler les lacunes avant l'examen blanc
  • S'entraîner sur des exercices de type examen
  • Maîtriser la lecture rapide de code inconnu

Modules et concepts à maîtriser

ModuleConcepts clés
M1let/const, types (string, number, boolean), template literals `${var}`
M2if/else if/else, opérateurs (===, &&, ||), switch/case, ternaire
M3for, for...of, while, break/continue
M4Déclarer et appeler une fonction, return, paramètres par défaut, arrow function
M5-M6Tableaux : push, filter, sort, map — Objets : .prop et ["prop"]
M7querySelector, querySelectorAll, .innerHTML, .textContent, .classList
M8addEventListener, event.preventDefault(), .value, .focus(), .reset()

Étape 1 — Kahoot révision (~20 min)

Révision ludique des 8 modules.

Étape 2 — Exercices de lecture de code (~30 min)

Lire chaque extrait et prédire le résultat sans exécuter. Exécuter mentalement ligne par ligne, comme le fait l'interpréteur JavaScript.

Exercice 1 — Tableaux et chaînage

js
const nombres = [3, 7, 1, 9, 4];
const resultat = nombres.filter(n => n > 4).sort((a, b) => b - a);
console.log(resultat);
Réponse

filter(n => n > 4)[7, 9].sort((a, b) => b - a) → tri décroissant → [9, 7]

Exercice 2 — Fonctions et paramètre par défaut

js
function saluer(prenom = "inconnu") {
  return `Bonjour ${prenom} !`;
}
console.log(saluer());
console.log(saluer("Alice"));
Réponse

saluer()"Bonjour inconnu !"saluer("Alice")"Bonjour Alice !"

Exercice 3 — Événements et formulaire

js
document.getElementById("btn").addEventListener("click", (e) => {
  e.preventDefault();
  const val = document.getElementById("search").value.trim().toLowerCase();
  console.log(val);
});
Réponse

Au clic sur #btn : empêche le comportement par défaut, lit la valeur de #search, supprime les espaces en début/fin, convertit en minuscules, affiche dans la console.

Exercice 4 — Template literals et conditions

js
const score = 4.2;
const mention = score >= 4 ? "Réussi" : "Échoué";
console.log(`Score : ${score} → ${mention}`);
Réponse

score >= 4truemention = "Réussi" Affiche : Score : 4.2 → Réussi

Exercice 5 — Boucle et tableau d'objets

js
const films = [
  { titre: "Inception", note: 9 },
  { titre: "Interstellar", note: 8 },
  { titre: "Tenet", note: 6 }
];

const bons = films.filter(f => f.note >= 8);
bons.forEach(f => console.log(f.titre));
Réponse

filter(f => f.note >= 8) garde Inception (9) et Interstellar (8). Affiche :

Inception
Interstellar

Étape 3 — Questions / réponses (~20 min)

Tour de table : chaque élève pose une question sur ce qu'il ne comprend pas encore.

Étape 4 — Refactorisation du CSS : pattern DRY (~30 min)

Cette étape introduit un principe fondamental du développement : DRYDon't Repeat Yourself — « ne te répète pas ».

Constat : trois boutons, trois fois le même code

Dans votre projet, vous avez maintenant trois boutons : Ajouter, Trier et Supprimer. Si vous regardez votre CSS, vous remarquerez que les trois ont des règles très similaires :

css
#btn-sort {
  padding: 8px 16px;
  background-color: #4a90d9;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.95rem;
  /* etc. */
}

form button[type="submit"] {
  padding: 10px 20px;
  background-color: #0078d4;  /* ⚠️ bleu différent — incohérent */
  color: white;
  border: none;
  border-radius: 4px;          /* ⚠️ rayon différent — incohérent */
  cursor: pointer;
  /* etc. */
}

.btn-delete {
  padding: 6px 12px;
  background-color: #e74c3c;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  /* etc. */
}

5 propriétés identiques (color: white, border: none, cursor: pointer, etc.) répétées 3 fois. Et en plus : des incohérences (deux bleus différents, deux border-radius).

Solution : extraire dans une classe .btn + variantes

css
/* ===== Boutons — pattern DRY =====
   Tous les boutons partagent la même base.
   .btn-primary et .btn-danger n'ajoutent que la couleur. */
.btn {
  padding: 8px 16px;
  font-size: 0.95rem;
  font-weight: 500;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 0.15s ease;
}

/* Variante : action positive (bleu) */
.btn-primary {
  background-color: #4a90d9;
}
.btn-primary:hover {
  background-color: #357ab8;
}

/* Variante : action destructive (rouge) */
.btn-danger {
  background-color: #c0392b;
}
.btn-danger:hover {
  background-color: #a02b1f;
}

Modifier le HTML : appliquer deux classes

html
<!-- Avant -->
<button type="submit">Ajouter</button>
<button id="btn-sort">Trier...</button>

<!-- Après — deux classes : .btn pour la base, .btn-primary pour la couleur -->
<button type="submit" class="btn btn-primary">Ajouter</button>
<button id="btn-sort" class="btn btn-primary">Trier...</button>

Modifier le JS : bouton Supprimer généré dans displayItems()

js
// Avant
html += `<button class="btn-delete">Supprimer</button>`;

// Après — trois classes : .btn .btn-danger pour le style, .btn-delete pour le rôle (JS)
html += `<button class="btn btn-danger btn-delete">Supprimer</button>`;

La classe .btn-delete est conservée car elle sert d'identifiant sémantique pour la délégation JS (event.target.closest(".btn-delete")).

Nettoyer l'ancien CSS

Supprimer les blocs #btn-sort, form button[type="submit"] et la partie style de .btn-delete (garder uniquement le positionnement, ex: margin-top).

Tester

  1. Ouvrir la page → les trois boutons doivent avoir la même taille, le même rayon, la même police
  2. Survoler chaque bouton → effet hover cohérent
  3. Cliquer "Supprimer" → la délégation JS continue de fonctionner (la classe .btn-delete est toujours présente)

Bénéfices du pattern DRY

  • Cohérence garantie : impossible d'avoir deux bleus différents — il n'y en a qu'un
  • Maintenance : changer la convention (ex: border-radius: 8px) → un seul endroit
  • Lisibilité du HTML : class="btn btn-primary" dit clairement l'intention
  • Réutilisable : pattern identique dans Bootstrap, Tailwind, Material UI…

Code de la démo formateur

La branche main du dépôt démo contient cette refacto. Comparer son code avant/après avec le commit refactor(css): extraire .btn.

Conseils pour l'examen

  • Connaître par cœur : addEventListener, preventDefault, filter, sort, template literals ` et data-id
  • Pour la lecture de code : exécuter mentalement ligne par ligne — ne pas essayer de deviner le résultat global d'un coup
  • Pour l'écriture de code : écrire les noms complets (addEventListener, pas addEvent) — l'orthographe compte

Rappel dates

  • 27 mai : examen blanc (séance 15)
  • 7 juin (23h59) : rendu final du projet
  • 9 juin : examen de module (30 min)

Ressources :

Séance 15 - 27 mai 2026

EXAMEN BLANC

Format (~1h30) :

  • Questions théoriques (QCM, vrai/faux)
  • Lecture et analyse de code
  • Écriture de code (exercices pratiques)
  • Couvre tous les modules 1-8

Dates importantes

  • 7 juin 2026 (23h59) : rendu final du projet sur GitHub
  • 9 juin 2026 : examen de module (30 min)

Récapitulatif des objectifs

Objectif officielModule(s)Couvert en C122 ?
Enjeux de la programmation navigateurM1, M7
Syntaxe de base du langageM1-M4
Gérer des listes et structures de donnéesM5-M6
Manipuler dynamiquement les éléments (DOM)M7
Créer et manipuler les formulairesM8
Gérer les interactions utilisateurM8
Gérer les erreursM9⏭ Reporté en C141
Consommer une API (CRUD)M9-M10⏭ Reporté en C141

Pour aller plus loin (optionnel)

Nuxy contient deux modules au-delà du programme C122 :

Module 9 — Promesses & API

  • Comprendre les Promise et async/await
  • Récupérer des données avec fetch() (GET)
  • Gérer les erreurs API
  • Envoyer/modifier/supprimer des données (POST, PUT, DELETE)

Module 10 — Mini-projet guidé

  • Construire une mini-app produits avec API CRUD complète
  • Recherche, tri, filtre par catégorie
  • Ajout/modification/suppression depuis un formulaire

Pour les élèves qui continuent en ESIG2

Les modules 9 et 10 anticipent ce que vous ferez en C141 (Vue.js) : appels API, gestion d'erreurs, formulaires CRUD. Si vous avez fini votre projet C122 en avance, faire ces deux modules vous donnera une longueur d'avance pour le C141.

Hors-scope examen C122

Ces modules ne seront pas évalués à l'examen de module C122 (9 juin 2026, 30 min) — l'examen porte uniquement sur M1-M8.

Documentation pour les cours de développement web