Plan d'enseignement - Cours 122
Vue d'ensemble des 15 séances
| Séance | Date | Module | Contenu | Exercices Nuxy |
|---|---|---|---|---|
| 1 ✓ | 21.01 | M1 | Bases JavaScript | 1.1 - 1.9 |
| 2 ✓ | 28.01 | M2 | Conditions | 2.1 - 2.6 |
| 3 ✓ | 04.02 | M3 | Boucles | 3.1 - 3.4 |
| 4 ✓ | 11.02 | M4 | Fonctions | 4.1 - 4.4 |
| 5 ✓ | 25.02 | M5 | Tableaux (bases) | 5.1 - 6.10 |
| 6 ✓ | 04.03 | M1-M7 | Démo CinéJS + Intro DOM | 7.1 - 7.7 |
| 7 ✓ | 11.03 | M7 | DOM | Terminer M7 |
| 8 ✓ | 25.03 | Projet | Présentation projet + validation ressource | 8.1 - 8.4 |
| 9 ✓ | 01.04 | Projet | Copilot + dépôt + données + premier affichage | 8.5 - 8.8 |
| 10 ✓ | 22.04 | Projet | Affichage dynamique (cartes) + CSS | Rattrapage |
| 11 ✓ | 29.04 | Projet | Tri et recherche | Rattrapage |
| 12 ✓ | 06.05 | Projet | Ajout et suppression | Tout terminé |
| 13 | 13.05 | Projet | Finalisation et responsive design | - |
| 14 | 20.05 | - | Révisions théoriques | - |
| 15 | 27.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
letetconst - 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
foretfor...of - Comprendre
do...whileetbreak
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 avecfilter()
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
- Ouvrir le dépôt template : github.com/fallinov/esig-122-demo-films
- Cliquer sur le bouton vert "Use this template" → "Create a new repository"

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

Étape 2 — Cloner et ouvrir dans WebStorm
- Copier l'URL de votre dépôt :
https://github.com/VOTRE-PSEUDO/esig-122-demo-films.git

- Dans WebStorm : File → New → Project from Version Control...
- Coller l'URL et cliquer sur Clone
- Ouvrir
index.htmldans 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
filmsavec 16 films - Phase 2 — Affichage console :
afficherFilmsConsole()avecfor...of,if/else, template literals - Phase 3 — Tri :
trierParNote()avec spread[...]etsort() - Phase 4 — Recherche :
rechercherFilm()avecfilter(),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
| Module | Concepts |
|---|---|
| M1 | Variables, const, template literals |
| M2 | Conditions, if/else |
| M3 | Boucles for...of |
| M4 | Fonctions, paramètres, return |
| M5 | filter, sort, spread [...], includes, toLowerCase |
| M6 | Tableau 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) :
- Exercices 7.1 à 7.7 sur nuxy.ch
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()etafficherFilmsHTML()avecquerySelector,innerHTML,forEach - Phase 6 — Contrôles interactifs :
rafraichir()avecaddEventListener("input", ...), chaînage recherche → tri → affichage
Concepts DOM introduits
| Concept | Description |
|---|---|
querySelector() | Sélectionner un élément par son id ou sélecteur CSS |
innerHTML | Injecter du HTML généré avec des template literals |
addEventListener | Réagir aux actions de l'utilisateur (frappe clavier) |
.value | Lire 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.
- Étapes 1-4 : liaison JS/HTML,
querySelector,textContent, variables, fonctioncapturer() - Énoncé PokéCount
- Démo en ligne
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 :
| Date | Séance | Étape projet | Devoirs Nuxy |
|---|---|---|---|
| 25 mars | 8 | Présentation projet | Terminer M7 (DOM) |
| 1er avril | 9 | Copilot + dépôt + données + premier affichage | M8 : 8.1 à 8.4 (événements) |
| 22 avril | 10 | Affichage dynamique (cartes) | M8 : 8.5 à 8.8 (formulaires) |
| 29 avril | 11 | Tri et recherche | Rattrapage si retard |
| 6 mai | 12 | Ajout et suppression (formulaire) | Rattrapage si retard |
| 13 mai | 13 | Finalisation et responsive | Tout terminé |
| 20 mai | 14 | Révisions théoriques | — |
| 27 mai | 15 | Examen blanc | — |
| 7 juin (23h59) | — | Rendu final du projet | — |
| 9 juin | — | Examen 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) etfeat/(nouvelle fonctionnalité) - Fusionner puis supprimer la branche
- Pour ce projet solo : travailler directement sur
mainest toléré - Toujours faire Commit + Push (= backup sur GitHub)
Étape 1 — Créer le dépôt depuis le template (~5 min)
- Ouvrir le dépôt template : github.com/fallinov/esig-122-projet-template
- Cliquer sur le bouton vert "Use this template" → "Create a new repository"
- Repository name :
122-projet-perso-prenom-nom(ex :122-projet-perso-steve-fallet) - Visibilité : Public (ou privé, au choix)
- Cliquer sur "Create repository"
Étape 2 — Publier sur GitHub Pages (~5 min)
- Sur GitHub → Settings (du dépôt) → Pages (menu gauche)
- Source : Deploy from a branch
- Branch : main / dossier / (root)
- Cliquer Save
- 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)
- Copier l'URL de votre dépôt (pas celle du site GitHub Pages !)
- Dans WebStorm : File → New → Project from Version Control...
- 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)
- Settings → Plugins → onglet Marketplace → rechercher "GitHub Copilot" → Install
- Redémarrer WebStorm
- 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 :
| Mode | Usage |
|---|---|
| Ask | Poser une question, obtenir une explication |
| Agent | Lui donner une tâche, il modifie les fichiers |
| Plan | Planifier 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 :
# 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) : anglaisCe 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 :
- Écrire vous-même un objet exemple avec les propriétés que vous voulez
- Faire valider votre structure par l'enseignant
- Utiliser le prompt ci-dessous dans le chat Copilot (ou une autre IA) en remplaçant les
[...]
Prompt à copier dans Copilot / une IA
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
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
// 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 :
- Vérifier que la page fonctionne en local (ouvrir
index.htmldans le navigateur) - Commit + Push → vérifier sur GitHub Pages que le site est à jour
- 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 :
/**
* 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 :
// 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
<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: gridouflexboxavecflex-wrap) - Carte avec ombre, bordure arrondie, hover
- Image en haut, contenu en bas
- Responsive : 1 colonne mobile, 2-3 colonnes desktop
Exemple CSS minimal
#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)
- Vérifier le rendu en local et sur mobile (DevTools → responsive)
- Console propre (F12, pas d'erreurs)
- Commit + Push avec un message clair (ex :
feat: affichage en cartes avec CSS) - 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 :
// ❌ 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 :
- Lit tout le HTML existant dans le conteneur
- Le concatène avec votre nouveau morceau
- 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
// ✅ 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 :
- La variable
htmlaccumule le texte, on écrit dans le DOM une seule fois à la fin. - 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) :
<button id="btn-sort">Trier par note ↓</button>JavaScript :
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'originalArray.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()placeaaprèsb. - Si elle est négative,
aest placé avantb.
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 :
<input type="text" id="search" placeholder="Rechercher un jeu...">JavaScript :
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 champArray.filter(): garder seulement les éléments qui correspondentString.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"> :
<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 :
.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.
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)
- Tester le tri et la recherche dans le navigateur
- Vérifier la console (F12) : aucune erreur
- Commit + Push avec un message clair (ex :
feat: tri et recherche) - 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 :
// ❌ 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") :
// ✅ 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 :
<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 :
.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 champtype="number"+min/max: validation native du navigateurrequired: le navigateur refuse la soumission si le champ est videtype="submit"déclenche l'événementsubmitsur le<form>parent
Étape 2 — Intercepter la soumission et ajouter au tableau (~25 min)
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 superflusNumber(): convertir la chaîne en nombre ("8"→8) — les.valuesont toujours des stringsDate.now(): horodatage en millisecondes, unique à chaque soumission → bon ID temporaireform.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 :
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 :
.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 :
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 :
// 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) :
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 (ounull)card.dataset.id: lire l'attributdata-idde la carteNumber(...):dataset.idest une string, il faut la convertir pour comparer avec===confirm(): boîte de dialogue native (retournetrueoufalse)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)
- Tester l'ajout : remplir → soumettre → carte apparaît → formulaire vidé
- Tester la suppression : clic Supprimer → confirmation → carte disparaît
- Tester la combinaison : ajouter, trier, rechercher, puis supprimer → tout reste cohérent
- Console (F12) : zéro erreur
- Commit + Push (ex :
feat: ajout et suppression) - 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 :
- Créer des formulaires
- Récupérer les valeurs des champs
- Valider les saisies
- Envoyer un formulaire
- Événements DOM
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 :
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 :
<div id="list"></div>et non :
<ul id="list"></ul>CSS pour le message :
.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 :
/* @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.
<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 :
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 :
# [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).
<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 :
/* 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 :
.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 :
- Console F12 : zéro erreur et zéro avertissement
- 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
- Tester sur mobile (DevTools → 375px) : aucun débordement
- Commit + Push (ex :
feat: finalisation — responsive, validation, README) - 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 :
- Formulaires — Valider les saisies
- CSS Media queries (MDN)
- Rédiger un bon README
- WCAG — Contraste des couleurs
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
| Module | Concepts clés |
|---|---|
| M1 | let/const, types (string, number, boolean), template literals `${var}` |
| M2 | if/else if/else, opérateurs (===, &&, ||), switch/case, ternaire |
| M3 | for, for...of, while, break/continue |
| M4 | Déclarer et appeler une fonction, return, paramètres par défaut, arrow function |
| M5-M6 | Tableaux : push, filter, sort, map — Objets : .prop et ["prop"] |
| M7 | querySelector, querySelectorAll, .innerHTML, .textContent, .classList |
| M8 | addEventListener, 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
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
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
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
const score = 4.2;
const mention = score >= 4 ? "Réussi" : "Échoué";
console.log(`Score : ${score} → ${mention}`);Réponse
score >= 4 → true → mention = "Réussi" Affiche : Score : 4.2 → Réussi
Exercice 5 — Boucle et tableau d'objets
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 : DRY — Don'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 :
#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
/* ===== 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
<!-- 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()
// 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-deleteest 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
- Ouvrir la page → les trois boutons doivent avoir la même taille, le même rayon, la même police
- Survoler chaque bouton → effet hover cohérent
- Cliquer "Supprimer" → la délégation JS continue de fonctionner (la classe
.btn-deleteest 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`etdata-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, pasaddEvent) — 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 officiel | Module(s) | Couvert en C122 ? |
|---|---|---|
| Enjeux de la programmation navigateur | M1, M7 | ✅ |
| Syntaxe de base du langage | M1-M4 | ✅ |
| Gérer des listes et structures de données | M5-M6 | ✅ |
| Manipuler dynamiquement les éléments (DOM) | M7 | ✅ |
| Créer et manipuler les formulaires | M8 | ✅ |
| Gérer les interactions utilisateur | M8 | ✅ |
| Gérer les erreurs | M9 | ⏭ 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
Promiseetasync/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.