# ✅ Solutions — rattrapage services (`PROCHAINE_ETAPE_services_2.md`)
> 🛑 **À NE REGARDER QU'EN DERNIER RECOURS.**
> Cherche d'abord par toi-même (au moins 10–15 min, et regarde la doc MDN). Recopier une solution sans l'avoir cherchée, ça ne s'apprend pas. Quand tu lis une réponse ici : lis-la, **ferme le fichier**, et réécris-la **de mémoire**. Si tu y arrives, c'est gagné.
Le code ci-dessous est **un** exemple correct. Le tien peut être écrit autrement et être tout aussi bon.
---
## 🩹 Préambule — les 3 corrections de ton `devis.js`
**1. L'attribut de la textarea** : c'est `rows` (pluriel), pas `row`.
```html
```
**2. Bloquer AVANT d'afficher** : on teste la limite d'abord, on affiche ensuite.
```js
boutonPlus.addEventListener("click", function () {
if (nbVehicules < 100) { // on teste AVANT
nbVehicules = nbVehicules + 1;
}
mettreAJour(); // on affiche APRÈS (jamais 100 puis retour)
});
```
**3. Le total se recalcule, il ne se stocke pas.** Supprime `totalargent` et `mettreAJourTOTAL`. Une seule fonction d'affichage suffit :
```js
function mettreAJour() {
affichageNb.textContent = nbVehicules;
affichageGeo.textContent = nbVehicules * PRIX_GPS;
affichageConso.textContent = nbVehicules * PRIX_CONSOMMATION;
}
```
> 💬 *« Pourquoi pas une variable `totalargent` ? »* Parce qu'elle pourrait se désynchroniser de `nbVehicules`. Une valeur qui se déduit d'une autre, on la **calcule au moment de l'afficher** : impossible qu'elle soit fausse.
---
## 🟢 Exo 1 — Singulier / pluriel
Dans le HTML, mets le mot dans son propre span, à côté du nombre :
```html
1véhicule
```
Dans `mettreAJour()` :
```js
motVehicule.textContent = nbVehicules === 1 ? "véhicule" : "véhicules";
```
(avec `const motVehicule = document.getElementById("mot_vehicule");` en haut.)
> 💬 *« `=`, `==` ou `===` ? »* `=` **range** une valeur. `===` **compare** (et vérifie aussi le type). Dans un `if`/un test, utilise toujours `===`.
---
## 🟢 Exo 2 — Compteur de caractères
HTML, sous la textarea :
```html
Il vous reste 500 caractères.
```
JS :
```js
const zoneMsg = document.getElementById("msg");
const restant = document.getElementById("restant");
const MAX = 500;
zoneMsg.addEventListener("input", function () {
const reste = MAX - zoneMsg.value.length;
restant.textContent = reste;
restant.style.color = reste < 50 ? "red" : "black"; // bonus
});
```
> 💬 *« Pourquoi `input` et pas `click` ? »* `click` ne se déclenche qu'au clic de souris ; `input` se déclenche à **chaque modification** du champ (clavier, collage…). C'est ce qu'on veut pour un compteur.
---
## 🟢 Exo 3 — Bloquer l'envoi vide
```js
const formulaire = document.querySelector("form");
formulaire.addEventListener("submit", function (evenement) {
if (zoneMsg.value.trim() === "") {
evenement.preventDefault();
alert("Merci d'écrire une suggestion avant d'envoyer !");
}
});
```
> 💬 *« À quoi sert `trim()` ? »* Il enlève les espaces au début/à la fin. Sans lui, « 3 espaces » compterait comme un message valide.
> 💬 *« `preventDefault`, c'est quoi ? »* Ça annule l'action par défaut du navigateur (ici : envoyer le formulaire et recharger la page).
---
## 🟡 Exo 4 — Tableau d'objets
Dans un fichier `services.js` (ou en haut de `devis.js`) :
```js
const services = [
{ nom: "Géolocalisation", description: "Suivi en temps réel", prix: "30€/véhicule/mois" },
{ nom: "Odomètre", description: "Kilométrage précis", prix: "200€" },
{ nom: "Consommation", description: "Carburant consommé", prix: "15€/véhicule/mois" },
{ nom: "Impact carbone", description: "CO₂ rejeté", prix: "30€/véhicule/mois" }
];
for (const service of services) {
console.log(service.nom + " — " + service.prix);
}
```
---
## 🟡 Exo 5 — Générer les lignes du tableau
HTML : garde le ``, vide le corps et donne-lui un id :
```html
```
JS (version `innerHTML` + template literal) :
```js
const corps = document.getElementById("corps_services");
let lignes = "";
for (const s of services) {
lignes += `
${s.nom}
${s.description}
${s.prix}
`;
}
corps.innerHTML = lignes;
```
> 💬 *« Pourquoi mes `${}` ne marchent pas ? »* Vérifie que tu utilises bien les accents **graves** `` ` `` (touche AltGr+7), pas des guillemets simples `'`.
---
## 🟡 Exo 6 — Recherche
Mets l'affichage dans une fonction réutilisable :
```js
function afficherServices(liste) {
let lignes = "";
for (const s of liste) {
lignes += `
${s.nom}
${s.description}
${s.prix}
`;
}
corps.innerHTML = lignes;
}
afficherServices(services);
const champ = document.getElementById("recherche"); // dans le HTML
champ.addEventListener("input", function () {
const mot = champ.value.toLowerCase();
const resultats = services.filter(function (s) {
return s.nom.toLowerCase().includes(mot);
});
afficherServices(resultats);
});
```
> 💬 *« `filter` modifie-t-il `services` ? »* Non. `filter` renvoie une **nouvelle** liste ; l'originale reste intacte (c'est pour ça qu'effacer la recherche fait tout revenir).
---
## 🟡 Exo 7 — Se souvenir du nombre de véhicules
```js
// à la fin de mettreAJour(), on sauvegarde
localStorage.setItem("nbVehicules", nbVehicules);
// au chargement de la page, on relit
const sauvegarde = localStorage.getItem("nbVehicules");
if (sauvegarde !== null) {
nbVehicules = parseInt(sauvegarde); // ⚠️ reconvertir le TEXTE en NOMBRE
mettreAJour();
}
```
> 💬 *« Pourquoi `parseInt` ? »* `localStorage` ne stocke que du **texte**. Sans conversion, `"12" + 1` donnerait `"121"`. `parseInt("12")` redonne le nombre `12`.
---
## 🟠 Exo 8 — Mode sombre
CSS :
```css
body.sombre { background-color: #1d2733; color: white; }
```
JS (avec `` dans le HTML) :
```js
document.getElementById("theme").addEventListener("click", function () {
document.body.classList.toggle("sombre");
});
```
> 💬 *« `toggle` fait quoi exactement ? »* Si la classe est présente il l'enlève, sinon il l'ajoute. Un seul appel gère l'aller **et** le retour.
---
## 🟠 Exo 9 — Horloge
```html
```
```js
const horloge = document.getElementById("horloge");
function afficherHeure() {
horloge.textContent = new Date().toLocaleTimeString();
}
afficherHeure(); // tout de suite
setInterval(afficherHeure, 1000); // puis chaque seconde
```
> 💬 *« Pourquoi appeler `afficherHeure()` avant le `setInterval` ? »* Sinon la page reste vide pendant la 1ʳᵉ seconde, le temps que `setInterval` se déclenche la première fois.
---
## 🔴 Exo 10 — `fetch` d'un fichier JSON
`vehicules.json` (racine du projet) :
```json
[
{ "modele": "Renault Kangoo", "immatriculation": "AB-123-CD", "km": 45200 },
{ "modele": "Peugeot Partner", "immatriculation": "EF-456-GH", "km": 78900 }
]
```
JS :
```js
async function chargerFlotte() {
const message = document.getElementById("message_flotte");
const corps = document.getElementById("corps_flotte");
try {
const reponse = await fetch("../vehicules.json");
const vehicules = await reponse.json();
for (const v of vehicules) {
const tr = document.createElement("tr");
tr.textContent = `${v.modele} — ${v.immatriculation} — ${v.km} km`;
corps.append(tr);
}
} catch (erreur) {
message.textContent = "Impossible de charger la flotte 😕";
console.error(erreur);
}
}
chargerFlotte();
```
> 💬 *« J'ai l'erreur "CORS" / "Failed to fetch" ! »* Tu as ouvert la page en double-cliquant (`file://`). Lance `python3 -m http.server 8000` dans le dossier du projet et ouvre `http://localhost:8000/pages/services.html`.
> 💬 *« Mon JSON ne charge pas. »* Vérifie : guillemets **doubles** partout, et **pas** de virgule après le dernier élément.
---
## 🏁 Exo 11 — Navbar en composant
```js
function creerNavbar(pageActive) {
return `
FleetZen
`;
}
document.body.insertAdjacentHTML("afterbegin", creerNavbar("services"));
```
N'oublie pas d'**enlever** la navbar écrite en dur dans ton HTML, sinon tu en auras deux.
> 💬 *« Pourquoi un paramètre `pageActive` ? »* Pour que la **même** fonction serve sur toutes les pages : sur services tu appelles `creerNavbar("services")`, sur contact `creerNavbar("contact")`, et c'est le bon bouton qui est mis en valeur.
---
### 🆘 Encore bloqué après avoir lu la solution ?
Note précisément : *qu'est-ce que tu attendais, qu'est-ce qui se passe à la place, et qu'affiche la Console (F12) ?* Avec ces 3 infos, on débloque n'importe quoi en deux minutes.