Afficher les projets¶
L'essentiel en 3 points
main.jsattend les données deloadProjects(), puis génère une carte par projet. Ce code est le même pour toutes les sources (JSON local, Google Sheets, Airtable).- Une fonction
createProjectCard(project)transforme un projet en HTML. On l'applique ensuite à tout le tableau. - Pour une structure multipages,
project.html?id=cafe-du-coinlit le paramètreidavecURLSearchParams, puis retrouve le bon projet avecfind().
Avant de commencer
- Votre
loadProjects()doit déjà fonctionner : le testconsole.log(projects[0].title)affiche un titre dans la console. Sinon, retournez à la page de votre source. - Vous avez fait l'exercice « Du JSON à la carte » : c'est le même mécanisme. Cette page vous guide pour l'appliquer à votre portfolio.
Comment utiliser cette page
Cette page ne contient pas de code complet à copier-coller : votre portfolio n'a pas la même structure que celui du voisin. Vous y trouverez les étapes, les notions clés et des squelettes à compléter. Écrivez votre code vous-mêmes, et ouvrez le récap JS quand une notion vous échappe.
1. Le conteneur dans le HTML¶
Dans index.html, un conteneur vide là où les cartes doivent apparaître. Les cartes que vous aviez codées à la main en HTML/CSS servent maintenant de modèle : on les retire du HTML, et on reproduit leur structure en JavaScript.
<div class="projects__grid">
<!-- les cartes sont générées par js/main.js -->
</div>
Gardez votre CSS tel quel
Si le HTML généré par JavaScript utilise exactement les mêmes classes que vos cartes codées à la main, votre CSS s'applique sans aucune modification. Copiez la structure de votre carte actuelle avant de la retirer du HTML.
2. Une carte : js/components/project-card.js¶
Une fonction qui reçoit un projet et retourne le HTML de sa carte, avec un gabarit littéral : les accents graves ` et ${...} pour insérer une valeur. Revoir : Récap : gabarits littéraux
Le squelette, à compléter avec votre structure de carte et vos classes :
function createProjectCard(project) {
return `
<article class="project-card">
<h3 class="project-card__title">${project.title}</h3>
<!-- à vous : image, catégorie, année, description, lien ou bouton... -->
</article>
`;
}
À vérifier avant de continuer
Dans la console : createProjectCard(projects[0]) doit retourner une chaîne de HTML complète, avec les vraies valeurs de votre premier projet.
Selon votre structure de navigation, la carte se termine par :
- Multipages : un lien vers
project.html?id=suivi de l'iddu projet (section 5). - One-pager avec modale : un bouton qui garde l'
iddu projet dans un attributdata-id(section 4). - One-pager avec carrousel : même principe de carte, c'est votre composant carrousel qui les fait défiler.
Les propriétés optionnelles¶
Un projet sans link afficherait href="undefined". On n'affiche un élément que si la propriété existe, avec l'opérateur ternaire :
${condition ? `HTML si vrai` : ''}
Lu à voix haute : « si la condition est vraie, affiche ce HTML, sinon rien ». À vous de l'appliquer à project.link, project.video, etc. Revoir : Récap : opérateur ternaire
3. Toutes les cartes : js/main.js¶
main.js est le chef d'orchestre. Sa fonction init() fait 3 choses, dans cet ordre :
async function init() {
// 1. Sélectionner le conteneur .projects__grid
// 2. Attendre les projets : await loadProjects()
// 3. Insérer une carte par projet dans le conteneur
}
init();
Revoir : Récap : init()
Pour l'étape 3, deux façons, déjà vues dans l'exercice :
| Façon | Idée | Revoir |
|---|---|---|
forEach + += |
Pour chaque projet, ajouter sa carte au innerHTML du conteneur. |
Récap : forEach |
map() + join('') |
Transformer tout le tableau en cartes, coller le tout, puis l'insérer en une seule fois. | Récap : map et join |
Laquelle choisir?
Les deux fonctionnent. map() + join('') modifie le DOM une seule fois, ce qui est un peu plus efficace : c'est ce que vise l'indicateur de la grille d'évaluation sur le chargement efficace des données. Choisissez celle que vous comprenez et pouvez expliquer.
Gérer l'échec du chargement¶
Un chargement peut échouer : fichier introuvable, jeton invalide, pas de réseau. Sans gestion d'erreur, la section reste vide et le visiteur ne sait pas pourquoi. Entourez les étapes 2 et 3 :
| Syntaxe | Structure |
|---|---|
async / await |
try { ... } catch (error) { ... } |
.then() |
loadProjects().then(projects => { ... }).catch(error => { ... }) |
Revoir : Récap : gérer les erreurs
Dans le catch : un console.error(error) pour vous, et un court message dans le conteneur pour le visiteur.
Utilisez la même syntaxe que dans votre data.js.
Gabarits et innerHTML : seulement avec vos propres données
Insérer du texte dans innerHTML est sans danger ici parce que les données sont les vôtres. Avec du texte saisi par des visiteurs (un formulaire, des commentaires), ce serait une faille XSS, que vous avez vue dans vos cours précédents.
4. One-pager avec modale¶
Le principe : une seule modale, vide, réutilisée pour tous les projets. Au clic sur une carte, on la remplit avec le bon projet, puis on l'ouvre.
Dans index.html, l'élément natif <dialog>, qui gère déjà le fond assombri et la touche Échap :
<dialog class="modal">
<div class="modal__content"></div>
<button class="modal__close">Fermer</button>
</dialog>
Les étapes, dans init(), après l'insertion des cartes (pour avoir accès à projects) :
- Un seul écouteur
clicksur le conteneur des cartes, plutôt qu'un par carte : c'est la délégation d'événements. - Dans l'écouteur, retrouver le bouton cliqué, s'il y en a un.
- Lire son
data-id. - Retrouver le projet correspondant dans le tableau déjà chargé.
- Remplir
.modal__contentavec un gabarit littéral, puis ouvrir la modale. - Ailleurs : le bouton « Fermer » ferme la modale.
Les outils dont vous avez besoin :
| Notion | Ce que ça fait | Revoir |
|---|---|---|
event.target.closest('.votre-bouton') |
Retrouve le bouton cliqué, même si on a cliqué sur un élément à l'intérieur. null si le clic était ailleurs. |
Récap : délégation d'événements |
bouton.dataset.id |
Lit l'attribut data-id d'un élément. |
Récap : dataset |
projects.find(p => p.id === ...) |
Retourne le projet qui a cet id. Aucun nouveau fetch(). |
Récap : find |
modal.showModal() / modal.close() |
Ouvre et ferme un <dialog>. |
MDN : <dialog> |
5. Multipages : project.html et les paramètres d'URL¶
Le principe¶
Chaque carte pointe vers la même page, avec un paramètre différent :
project.html?id=cafe-du-coin
project.html?id=biome
Tout ce qui suit le ? s'appelle la chaîne de requête (query string). Elle est composée de paires nom=valeur. project.html est un gabarit vide : il lit l'id dans l'adresse, recharge les données, et affiche seulement le projet correspondant. Une seule page HTML pour tous vos projets, pas de routeur, pas de cadriciel.
Le HTML de project.html¶
Mêmes <head>, en-tête et pied de page que index.html, avec un conteneur vide pour le détail. Les scripts :
<script src="js/data.js" defer></script>
<script src="js/project.js" defer></script>
data.js est le même fichier que pour la page d'accueil : on réutilise loadProjects() sans rien changer.
Lire l'id dans l'adresse¶
C'est la seule notion vraiment nouvelle aujourd'hui :
const params = new URLSearchParams(window.location.search);
const projectId = params.get('id'); // "cafe-du-coin", ou null si absent
window.location.search: la partie de l'adresse qui commence au?, soit"?id=cafe-du-coin".new URLSearchParams(...): un objet qui sait lire cette chaîne..get('id'): la valeur du paramètreid.
Revoir : Récap : paramètres d'URL
Les étapes de js/project.js¶
async function showProject() {
// 1. Sélectionner le conteneur du détail
// 2. Lire l'id dans l'adresse (ci-dessus)
// 3. Attendre les projets : await loadProjects()
// 4. Retrouver LE projet qui a cet id, avec find()
// 5. S'il n'existe pas : message "introuvable" + lien de retour, puis return
// 6. Sinon : remplir le conteneur avec un gabarit littéral
}
showProject();
Sans oublier la gestion d'erreur, comme dans main.js.
Deux détails qui font professionnel
document.title: changez le titre de l'onglet pour celui du projet.- Le cas « introuvable » (étape 5) : quelqu'un peut toujours taper une adresse erronée. Testez-le avec
project.html?id=nimporte-quoi.
Chaque id doit être unique
Deux projets avec le même id, et find() affichera toujours le premier. Pas d'espaces ni d'accents non plus : un id se retrouve dans une adresse web.
Bonus : afficher la galerie¶
project.gallery est un tableau d'URL. C'est le même patron que pour les cartes, mais à l'intérieur d'un gabarit : transformer chaque URL en <img>, puis coller le tout. Et seulement si la galerie existe (ternaire).
6. Pièges fréquents¶
undefinedaffiché dans la page : une propriété est mal nommée (project.Titleau lieu deproject.title) ou absente de vos données. Comparez avec unconsole.log(project).loadProjects is not defined:data.jsn'est pas lié dans cette page, ou il est lié après le script qui l'utilise.- Une seule carte s'affiche, la dernière :
=au lieu de+=dans une boucleforEach. - Les cartes s'affichent mais sans style : les classes générées en JS ne correspondent pas exactement à celles de votre CSS.
- Rien ne s'affiche, aucune erreur : vérifiez que le sélecteur de votre
querySelector()existe vraiment dans le HTML de cette page. - Le clic sur une carte ne fait rien : l'écouteur est placé avant l'insertion des cartes, ou le sélecteur de
closest()ne correspond pas à la classe de votre bouton. - Texte alternatif :
alt="${project.title}"est un minimum. Pour de meilleures descriptions, ajoutez une propriétéaltà vos données.
7. D'ici le prochain cours¶
- Les cartes de la page d'accueil sont générées à partir de vos données, plus aucune n'est écrite à la main dans le HTML.
- Le détail d'un projet s'affiche (modale, carrousel ou
project.html). - Un projet sans
linkou sansvideon'affiche pasundefined. - Vous pouvez expliquer chaque ligne de votre code, sans l'aide de Copilot.
- Commit avec un message clair, ex.
Chargement des projets depuis projects.json. - Entrée dans
JOURNAL.mdpour chaque prompt Copilot délibéré (pas les complétions en ligne).