Tuto JS niveau 3 contact: tableaux, boucles, recherche/tri, localStorage, classList, FAQ, fonctions parametrees
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
# 🚀 Niveau 3 — JavaScript : tableaux, boucles & pages vivantes
|
||||
|
||||
Salut Mélissa ! 👋
|
||||
|
||||
Tu as fini tout le Niveau 2 (conditions, singulier/pluriel, envoi bloqué si vide, bouton « Effacer », badge horaires, navbar en composant). On enchaîne.
|
||||
|
||||
> 🔎 Détail au passage : ton `restant <= 1 ? "caractère" : "caractères"` est correct — en français, **0 prend le singulier** (« 0 caractère »).
|
||||
|
||||
Ce fichier est une série d'exercices **qui s'enchaînent**, chacun introduisant **une notion de plus**. Fil rouge : rendre la page **vivante et pilotée par des données**. Il y en a beaucoup, de quoi tenir l'après-midi — tu n'es pas obligée de tout finir.
|
||||
|
||||
> 🧭 **Mode d'emploi** (toujours le même) :
|
||||
> - Avance **dans l'ordre**, un exo à la fois.
|
||||
> - **Teste** (✅) avant de passer au suivant.
|
||||
> - Garde la **Console (F12)** ouverte : c'est ton tableau de bord.
|
||||
> - Bloquée ? C'est **normal** : note ta question et on en parle.
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Le programme de l'après-midi
|
||||
|
||||
| Module | Tu vas apprendre… | Exos |
|
||||
|---|---|---|
|
||||
| **A. Tableaux & boucles** | stocker une liste, la parcourir, générer du HTML automatiquement | 1 → 4 |
|
||||
| **B. Manipuler les listes** | rechercher, trier, filtrer | 5 → 6 |
|
||||
| **C. La mémoire** | sauvegarder des infos qui survivent au rechargement | 7 → 8 |
|
||||
| **D. Le style dynamique** | changer l'apparence au clic, faire un accordéon | 9 → 10 |
|
||||
| **E. Pour finir en beauté** | factoriser, paramétrer, défis libres | 11 → 12 |
|
||||
|
||||
---
|
||||
|
||||
# 🧱 Module A — Les tableaux et les boucles
|
||||
|
||||
Jusqu'ici tes variables contenaient **une seule** valeur (`const MAX = 500`). Mais comment ranger **une liste** ? Le nom de tes 2 développeurs, par exemple ? → avec un **tableau** (`array`).
|
||||
|
||||
## 🧩 La notion : un tableau, c'est une liste
|
||||
|
||||
```js
|
||||
const fruits = ["pomme", "banane", "cerise"];
|
||||
|
||||
console.log(fruits[0]); // "pomme" → on compte à partir de 0 !
|
||||
console.log(fruits.length); // 3 → combien d'éléments
|
||||
```
|
||||
|
||||
> 🔎 **Point qui surprend tout le monde au début :** on compte **à partir de 0**. Le 1ᵉ élément est `fruits[0]`, le 2ᵉ est `fruits[1]`… Le dernier est donc `fruits[fruits.length - 1]`.
|
||||
|
||||
## 🧩 La notion : une boucle, pour répéter sans copier-coller
|
||||
|
||||
Pour faire quelque chose **avec chaque** élément d'une liste, on utilise une **boucle** `for...of` :
|
||||
|
||||
```js
|
||||
for (const fruit of fruits) {
|
||||
console.log("J'aime la " + fruit);
|
||||
}
|
||||
// affiche 3 lignes, une par fruit
|
||||
```
|
||||
|
||||
> 💡 **À retenir :** `for...of` se lit « **pour chaque** *fruit* **parmi** *fruits*, fais… ». La boucle s'occupe de passer d'un élément au suivant toute seule.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Exo 1 — Ta première liste, dans la console
|
||||
|
||||
Crée un fichier `equipe.js` (on garde ton `contact.js` pour le formulaire). Branche-le dans ta page, **avant** `contact.js`, juste avant `</body>` :
|
||||
|
||||
```html
|
||||
<script src="../equipe.js"></script>
|
||||
<script src="../contact.js"></script>
|
||||
```
|
||||
|
||||
Dans `equipe.js` :
|
||||
|
||||
```js
|
||||
const equipe = ["Mélissa", "Guillaume"];
|
||||
|
||||
for (const personne of equipe) {
|
||||
console.log("Membre de l'équipe : " + personne);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Teste :** ouvre la Console (F12). Tu dois voir 2 lignes. Ajoute un 3ᵉ prénom au tableau, recharge → 3 lignes, **sans toucher à la boucle**.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Exo 2 — Des données plus riches : les **objets**
|
||||
|
||||
Un prénom tout seul, c'est peu. Pour décrire une personne (nom **+** job **+** partie créée), on utilise un **objet** `{ … }` :
|
||||
|
||||
```js
|
||||
const membre = {
|
||||
nom: "Mélissa",
|
||||
job: "Stagiaire",
|
||||
partie: "Page contact"
|
||||
};
|
||||
|
||||
console.log(membre.nom); // "Mélissa"
|
||||
console.log(membre.partie); // "Page contact"
|
||||
```
|
||||
|
||||
Et une **liste d'objets** = un tableau d'objets. Remplace le contenu de `equipe.js` :
|
||||
|
||||
```js
|
||||
const equipe = [
|
||||
{ nom: "Mélissa", job: "Stagiaire", partie: "Page contact" },
|
||||
{ nom: "Guillaume", job: "Stagiaire", partie: "Page services" }
|
||||
];
|
||||
|
||||
for (const membre of equipe) {
|
||||
console.log(membre.nom + " — " + membre.job + " — " + membre.partie);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Teste :** la Console affiche une ligne par membre, avec les 3 infos.
|
||||
|
||||
> 🧩 **À retenir :** un **tableau** `[ ]` = une liste ordonnée. Un **objet** `{ }` = une fiche avec des **étiquettes** (`nom`, `job`…). Les deux combinés (`[ {…}, {…} ]`), c'est **la** façon de stocker des données dans presque toutes les applis.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Exo 3 — Générer ton tableau HTML **automatiquement**
|
||||
|
||||
C'est l'exercice central du module. Aujourd'hui, ton `<table>` d'équipe est **écrit à la main** dans le HTML. Problème : pour ajouter un membre, il faut retaper tout un `<tr>`. On va le faire **générer par le JavaScript** à partir du tableau `equipe`.
|
||||
|
||||
### Étape 1 — Préparer le HTML
|
||||
|
||||
Dans `contact.html`, garde l'en-tête du tableau (`<thead>`) mais **vide le corps** et donne-lui un `id` :
|
||||
|
||||
```html
|
||||
<table border="1" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Job</th>
|
||||
<th>Parties créées</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="corps_equipe">
|
||||
<!-- vide : le JavaScript va le remplir tout seul -->
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
### Étape 2 — Construire les lignes en JavaScript
|
||||
|
||||
Dans `equipe.js` :
|
||||
|
||||
```js
|
||||
const corps = document.getElementById("corps_equipe");
|
||||
let lignes = ""; // on va empiler le HTML dans ce texte
|
||||
|
||||
for (const membre of equipe) {
|
||||
lignes = lignes + `
|
||||
<tr>
|
||||
<td>${membre.nom}</td>
|
||||
<td>${membre.job}</td>
|
||||
<td>${membre.partie}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
corps.innerHTML = lignes; // on injecte tout d'un coup
|
||||
```
|
||||
|
||||
**✅ Teste :** ton tableau s'affiche comme avant, mais cette fois généré par le JS. La preuve : ajoute un 3ᵉ membre dans le tableau `equipe` (ex. `{ nom: "Matthieu", job: "Tuteur", partie: "Relecture" }`), recharge → la ligne apparaît toute seule.
|
||||
|
||||
> 🔎 Deux nouveautés ici :
|
||||
> - **Le `` `texte ${variable}` ``** (avec les accents graves `` ` ``) : ça s'appelle un *template literal*. Le `${…}` insère la valeur d'une variable **dans** le texte. (Tu l'utilises déjà dans `creerNavbar` 😉 !)
|
||||
> - **`.innerHTML = …`** remplace **tout le contenu HTML** d'un élément. Pratique, mais à réserver à **tes propres** données (jamais avec du texte tapé par un inconnu — question de sécurité, on en reparlera).
|
||||
|
||||
> 🔎 **Le principe à retenir :** on sépare les **données** (le tableau `equipe`) de l'**affichage** (la boucle qui fabrique le HTML). Change les données → l'affichage suit. C'est le mode de fonctionnement de React, Vue, etc.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Exo 4 — Compter et afficher un total
|
||||
|
||||
Au-dessus du tableau, ajoute dans le HTML :
|
||||
|
||||
```html
|
||||
<p>👥 L'équipe compte <span id="nb_membres"></span> personne(s).</p>
|
||||
```
|
||||
|
||||
Et en JS, après ta boucle :
|
||||
|
||||
```js
|
||||
document.getElementById("nb_membres").textContent = equipe.length;
|
||||
```
|
||||
|
||||
**✅ Teste :** le nombre correspond à la taille de ton tableau. Ajoute/enlève un membre → le compte suit tout seul.
|
||||
|
||||
> 💡 `equipe.length`, tu l'avais déjà croisé avec `zoneMessage.value.length` : `.length`, c'est « **combien** ». Sur un texte = nombre de caractères ; sur un tableau = nombre d'éléments.
|
||||
|
||||
---
|
||||
|
||||
# 🔎 Module B — Rechercher et trier dans une liste
|
||||
|
||||
## 🧪 Exo 5 — Une **barre de recherche** qui filtre l'équipe en direct
|
||||
|
||||
> 🆕 Nouvelles notions : `.filter()`, `.toLowerCase()`, `.includes()`, et **ré-afficher** quand les données changent.
|
||||
|
||||
D'abord, range ton affichage du tableau dans une **fonction** (pour pouvoir le rejouer) :
|
||||
|
||||
```js
|
||||
function afficherEquipe(liste) {
|
||||
let lignes = "";
|
||||
for (const membre of liste) {
|
||||
lignes += `
|
||||
<tr>
|
||||
<td>${membre.nom}</td>
|
||||
<td>${membre.job}</td>
|
||||
<td>${membre.partie}</td>
|
||||
</tr>`;
|
||||
}
|
||||
corps.innerHTML = lignes;
|
||||
}
|
||||
|
||||
afficherEquipe(equipe); // affichage initial : toute l'équipe
|
||||
```
|
||||
|
||||
> 🔎 `lignes += "…"` est un raccourci pour `lignes = lignes + "…"`. Pratique pour empiler.
|
||||
|
||||
Ajoute un champ de recherche dans le HTML, au-dessus du tableau :
|
||||
|
||||
```html
|
||||
<input type="search" id="recherche" placeholder="Rechercher un membre…">
|
||||
```
|
||||
|
||||
Puis en JS :
|
||||
|
||||
```js
|
||||
const champRecherche = document.getElementById("recherche");
|
||||
|
||||
champRecherche.addEventListener("input", function () {
|
||||
const motCherche = champRecherche.value.toLowerCase();
|
||||
|
||||
// on garde seulement les membres dont le nom contient le texte tapé
|
||||
const resultats = equipe.filter(function (membre) {
|
||||
return membre.nom.toLowerCase().includes(motCherche);
|
||||
});
|
||||
|
||||
afficherEquipe(resultats); // on ré-affiche, mais filtré
|
||||
});
|
||||
```
|
||||
|
||||
**✅ Teste :** tape « mé » → seule Mélissa reste. Efface → toute l'équipe revient.
|
||||
|
||||
> 🧩 **À retenir :**
|
||||
> - `.filter(...)` crée un **nouveau** tableau avec seulement les éléments qui passent le test.
|
||||
> - `.toLowerCase()` met en minuscules → la recherche ne tient pas compte des majuscules.
|
||||
> - `.includes("…")` répond vrai/faux : « ce texte contient-il … ? ».
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Exo 6 — Un bouton « Trier par nom »
|
||||
|
||||
> 🆕 Nouvelle notion : `.sort()`.
|
||||
|
||||
```html
|
||||
<button type="button" id="trier">Trier A → Z</button>
|
||||
```
|
||||
|
||||
```js
|
||||
document.getElementById("trier").addEventListener("click", function () {
|
||||
const triee = [...equipe].sort(function (a, b) {
|
||||
return a.nom.localeCompare(b.nom);
|
||||
});
|
||||
afficherEquipe(triee);
|
||||
});
|
||||
```
|
||||
|
||||
**✅ Teste :** clique → l'équipe s'affiche dans l'ordre alphabétique.
|
||||
|
||||
> 🔎 `[...equipe]` fait une **copie** du tableau (pour ne pas modifier l'original). `localeCompare` compare deux mots dans l'ordre alphabétique (et gère les accents français correctement). C'est un peu abstrait : retiens juste la **recette**, tu la comprendras à force de l'utiliser.
|
||||
|
||||
---
|
||||
|
||||
# 💾 Module C — Donner une mémoire à ta page
|
||||
|
||||
Recharge ta page : tout ce que l'utilisateur avait tapé **disparaît**. On va y remédier avec le **`localStorage`** : un petit coffre-fort dans le navigateur qui **survit au rechargement**.
|
||||
|
||||
## 🧪 Exo 7 — Sauvegarder le brouillon du message
|
||||
|
||||
> 🆕 Nouvelle notion : `localStorage.setItem(...)` / `localStorage.getItem(...)`.
|
||||
|
||||
Dans `contact.js`, ajoute : à **chaque frappe**, on sauvegarde le message.
|
||||
|
||||
```js
|
||||
zoneMessage.addEventListener("input", function () {
|
||||
localStorage.setItem("brouillon", zoneMessage.value);
|
||||
});
|
||||
```
|
||||
|
||||
> ⚠️ Tu as **déjà** un `addEventListener("input", rafraichirCompteur)` sur la même zone. Pas de souci : on peut en mettre plusieurs, ils s'exécutent tous les deux. (Encore mieux : ajoute la ligne `localStorage.setItem(...)` **à l'intérieur** de `rafraichirCompteur`.)
|
||||
|
||||
**✅ Teste :** tape un message, ouvre la Console et tape `localStorage.getItem("brouillon")` → tu vois ton texte.
|
||||
|
||||
## 🧪 Exo 8 — Recharger le brouillon au démarrage
|
||||
|
||||
Au tout début (au chargement de la page), on regarde si un brouillon existe :
|
||||
|
||||
```js
|
||||
const brouillon = localStorage.getItem("brouillon");
|
||||
if (brouillon) { // s'il y a quelque chose de sauvegardé
|
||||
zoneMessage.value = brouillon;
|
||||
rafraichirCompteur(); // pour que le compteur soit juste dès le départ
|
||||
}
|
||||
```
|
||||
|
||||
Et quand le message est envoyé avec succès, on vide le coffre (sinon le vieux brouillon revient) :
|
||||
|
||||
```js
|
||||
// à ajouter dans ton addEventListener("submit", …), quand le message N'EST PAS vide
|
||||
localStorage.removeItem("brouillon");
|
||||
```
|
||||
|
||||
**✅ Teste :** écris un message, **recharge la page** (F5) → ton texte est toujours là, et le compteur est correct.
|
||||
|
||||
> 💡 **À retenir :** `localStorage` garde des infos **entre les visites** (jusqu'à ce qu'on les efface). `setItem(clé, valeur)` pour ranger, `getItem(clé)` pour relire, `removeItem(clé)` pour effacer.
|
||||
|
||||
---
|
||||
|
||||
# 🎨 Module D — Changer l'apparence en JavaScript
|
||||
|
||||
## 🧪 Exo 9 — Un bouton « Mode sombre 🌙 »
|
||||
|
||||
> 🆕 Nouvelle notion : `classList.toggle(...)` — ajouter/retirer une classe CSS au clic.
|
||||
|
||||
D'abord, prépare la classe dans ton CSS (`contact.css`) :
|
||||
|
||||
```css
|
||||
body.sombre {
|
||||
background-color: #1d2733;
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
Un bouton dans le HTML :
|
||||
|
||||
```html
|
||||
<button type="button" id="theme">🌙 Mode sombre</button>
|
||||
```
|
||||
|
||||
Et le JS :
|
||||
|
||||
```js
|
||||
document.getElementById("theme").addEventListener("click", function () {
|
||||
document.body.classList.toggle("sombre");
|
||||
});
|
||||
```
|
||||
|
||||
**✅ Teste :** clique → la page bascule en sombre, re-clique → elle revient. Un seul `toggle` fait les deux !
|
||||
|
||||
> 🧩 **À retenir :** `classList.toggle("sombre")` = « si la classe est là, enlève-la ; sinon, ajoute-la ». C'est **le** pont entre ton JavaScript et ton CSS : le JS gère le *quand*, le CSS gère le *à quoi ça ressemble*.
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Exo 10 — Une FAQ en accordéon
|
||||
|
||||
> 🆕 Nouvelles notions : `querySelectorAll(...)` (sélectionner **plusieurs** éléments) + une boucle `forEach`.
|
||||
|
||||
Beaucoup de pages contact ont une FAQ. Ajoute dans le HTML :
|
||||
|
||||
```html
|
||||
<section class="faq">
|
||||
<h3>Questions fréquentes</h3>
|
||||
<div class="question">
|
||||
<button class="q-titre" type="button">Comment suivre ma flotte en temps réel ?</button>
|
||||
<p class="q-reponse">Grâce à notre service de géolocalisation, accessible 24h/24.</p>
|
||||
</div>
|
||||
<div class="question">
|
||||
<button class="q-titre" type="button">Puis-je résilier à tout moment ?</button>
|
||||
<p class="q-reponse">Oui, sans frais, depuis votre espace client.</p>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
Dans le CSS, on cache les réponses au départ :
|
||||
|
||||
```css
|
||||
.q-reponse { display: none; }
|
||||
.q-reponse.ouverte { display: block; }
|
||||
```
|
||||
|
||||
En JavaScript, on veut que **chaque** bouton ouvre/ferme **sa** réponse :
|
||||
|
||||
```js
|
||||
const titres = document.querySelectorAll(".q-titre"); // TOUS les boutons FAQ
|
||||
|
||||
titres.forEach(function (titre) {
|
||||
titre.addEventListener("click", function () {
|
||||
// la réponse est le paragraphe juste après le bouton
|
||||
const reponse = titre.nextElementSibling;
|
||||
reponse.classList.toggle("ouverte");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**✅ Teste :** clique sur une question → sa réponse apparaît ; re-clique → elle se cache. Chaque question est indépendante.
|
||||
|
||||
> 🧩 **À retenir :**
|
||||
> - `getElementById` attrape **un** élément ; `querySelectorAll(".classe")` en attrape **plusieurs** (tous ceux qui ont cette classe).
|
||||
> - `.forEach(...)` est une boucle qui fait quelque chose **pour chaque** élément trouvé — ici : « ajoute un écouteur de clic à chacun ».
|
||||
> - `nextElementSibling` = « l'élément juste après moi ». Pratique pour relier un bouton à sa réponse.
|
||||
|
||||
---
|
||||
|
||||
# 🏁 Module E — Pour finir en beauté
|
||||
|
||||
## 🧪 Exo 11 — Une navbar qui sait quelle page est active
|
||||
|
||||
Ta `creerNavbar()` est déjà un beau composant. Rendons-la **paramétrable** : on lui dit quelle page est active, et elle met le bon bouton en surbrillance.
|
||||
|
||||
> 🆕 Nouvelle notion : une fonction qui prend un **paramètre**.
|
||||
|
||||
```js
|
||||
function creerNavbar(pageActive) { // ← pageActive est le "réglage" qu'on lui donne
|
||||
return `
|
||||
<div id="nav_barre" class="shadow">
|
||||
<h1>FleetZen</h1>
|
||||
<div id="buttons">
|
||||
<a href="contact.html"><button class="${pageActive === "contact" ? "instyled" : "styled"}">contact</button></a>
|
||||
<a href="services.html"><button class="${pageActive === "services" ? "instyled" : "styled"}">services</button></a>
|
||||
<a href="../index.html"><button class="styled">retour</button></a>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
document.body.insertAdjacentHTML("afterbegin", creerNavbar("contact"));
|
||||
```
|
||||
|
||||
**✅ Teste :** sur la page contact, le bouton « contact » apparaît enfoncé (`instyled`). Le jour où Guillaume réutilise cette fonction sur sa page, il écrira juste `creerNavbar("services")` → c'est SON bouton qui sera actif.
|
||||
|
||||
> 💡 **À retenir :** un **paramètre** (`pageActive`), c'est un réglage qu'on passe à une fonction pour qu'elle s'adapte. Une seule fonction, plusieurs résultats selon ce qu'on lui donne. C'est ÇA, un vrai composant réutilisable. ♻️
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Exo 12 — Défis libres (si tu as encore de l'énergie)
|
||||
|
||||
Choisis ceux qui t'attirent, dans l'ordre que tu veux :
|
||||
|
||||
1. **Recap avant envoi** : à la soumission, affiche « Merci {prénom}, ton message de {N} caractères a bien été pris en compte ! » en lisant la valeur du champ prénom.
|
||||
2. **Validation e-mail maison** : si l'e-mail ne contient pas de `@`, bloque l'envoi avec un message (en plus de la vérification du navigateur).
|
||||
3. **Compteur de mots** (en plus des caractères) : `zoneMessage.value.trim().split(" ").length`.
|
||||
4. **Ajouter un membre via un formulaire** : un petit champ + bouton qui fait `equipe.push({...})` puis `afficherEquipe(equipe)`.
|
||||
5. **Mémoriser le mode sombre** dans `localStorage` pour qu'il reste actif au rechargement.
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Récap — tout ce que tu sais faire maintenant
|
||||
|
||||
- **Données** : variables, **tableaux** `[ ]`, **objets** `{ }`, listes d'objets
|
||||
- **Répéter** : boucles `for...of` et `.forEach()`
|
||||
- **Transformer des listes** : `.filter()`, `.sort()`
|
||||
- **Générer du HTML** depuis des données (`innerHTML` + *template literals*)
|
||||
- **Mémoire** : `localStorage` (setItem / getItem / removeItem)
|
||||
- **Style dynamique** : `classList.toggle()`
|
||||
- **Sélection multiple** : `querySelectorAll` + `forEach`
|
||||
- **Fonctions paramétrées** : un composant qui s'adapte
|
||||
|
||||
Prochaine grande étape (pour une autre fois) : **les requêtes vers un serveur** avec `fetch`, pour aller chercher de vraies données en ligne.
|
||||
Reference in New Issue
Block a user