# ✅ 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 1 vé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 ` `; } 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.