Cours Vue.js · Module 2: Syntaxe des templates
Projet : panneau d'hologrammes interactif du NOVA LAB
Dans cette leçon3
C'est l'heure du projet final ! L'opérateur de l'atelier d'hologrammes veille sur une douzaine de projections : cartes, schémas du réacteur, données atmosphériques. Certaines sont éteintes, et de nouvelles arrivent pendant le service. Il lui faut un panneau qui affiche tous les hologrammes, les filtre selon leur état et réagit à chaque clic, tout en gardant les données à un seul endroit, pour que la liste, le filtre et le compteur ne se contredisent jamais.
Dans ce projet, tu vas combiner toutes les techniques de templates Vue apprises dans ce module : de l'affichage des données, au filtrage et au style, jusqu'à la gestion des événements et l'accès direct au DOM.
Résumé du module
Dans ce module, tu as appris :
- L'interpolation
{{ }}- afficher des données réactives et des expressions JavaScript dans les templates. N'oublie pas les limites : seulement des expressions, pas des instructions. - v-bind (
:) - liaison dynamique des attributs HTML ::src,:class,:style,:disabled. La syntaxe raccourcie:est le standard. - v-on (
@) - gestion des événements utilisateur :@click,@submit,@keyup. Les modificateurs comme.prevent,.stop,.oncesimplifient les opérations courantes. - Liaison de classes et de styles - syntaxe objet (
:class="{ active: isActive }"), syntaxe tableau (:class="[classA, classB]") et styles inline avec:style. - v-if / v-else-if / v-else / v-show - rendu conditionnel.
v-ifajoute/retire du DOM,v-showne bascule quedisplay. - v-for - rendu de listes avec
:key, itération sur des tableaux et des objets, en évitantv-foravecv-ifsur le même élément. - Template Refs - accès direct aux éléments du DOM via
ref, function refs, refs sur les composants avecdefineExpose. - v-html - affichage de HTML brut (attention au XSS !).
Comment aborder le projet
Commence par les données et n'ajoute le template qu'ensuite. Les hologrammes sont un tableau d'objets avec les champs id, name, type et active, conservé dans ref([...]). Tu affiches la liste avec v-for et :key="h.id". L'index ne convient pas comme clé : après un filtrage, le même numéro passe à un autre hologramme et Vue confond les cartes. C'est le même piège que le tri des capteurs dans la leçon sur v-for.
Le filtre est un ref séparé, par exemple filter avec les valeurs 'all', 'active' et 'inactive'. Un computed renvoie la liste filtrée en lisant filter et le tableau des hologrammes, et v-for parcourt justement ce computed. N'ajoute pas v-if sur l'élément qui porte v-for : v-if a une priorité plus haute et ne voit pas la variable de la boucle. Dans le squelette, filteredHolograms est un simple ref qui contient une copie du tableau, alors remplace-le par un computed.
L'apparence d'une carte dépend de son état : utilise donc :class="{ 'is-active': h.active, 'is-inactive': !h.active }" et décris les couleurs des bordures et la lueur néon dans le CSS. Garde :style pour les valeurs vraiment calculées à partir des données, comme la largeur d'une barre d'énergie.
Les événements relient le panneau à l'opérateur. @click sur une carte bascule son champ active, Entrée dans le champ du nom (@keyup.enter) et le bouton ajoutent un nouvel hologramme, et « Supprimer le dernier » appelle pop() sur le tableau. Le compteur « X actifs sur Y au total » est un autre computed, il se recalcule donc tout seul après chaque changement.
Reste le défilement : un nouvel hologramme arrive à la fin de la liste, souvent hors de la zone visible. C'est là que servent le template ref de la leçon précédente et la fonction nextTick, décrite au point 5 ci-dessous.
Exigences du projet
Construis un panneau de contrôle d'hologrammes qui inclut :
1. Liste d'hologrammes avec v-for
Affiche une liste des hologrammes de la station. Chaque hologramme a un nom, un type (ex. « navigation », « diagnostic », « communication ») et un statut (actif/inactif). Utilise :key avec un ID unique.
2. Filtrage avec computed
Ajoute trois boutons de filtre : « Tous », « Actifs », « Inactifs ». Selon le filtre choisi, affiche les hologrammes appropriés. Utilise une propriété calculée (computed) pour filtrer la liste.
3. Classes et styles dynamiques
Utilise :class pour que les hologrammes actifs aient une bordure verte (border-color: #00ff88), et les inactifs une bordure rouge (border-color: #ff4444). Ajoute un effet de lueur néon (box-shadow) pour les éléments actifs.
4. Gestion des événements
- Cliquer sur un hologramme bascule son statut (actif/inactif)
- Un bouton « Ajouter un hologramme » ajoute à la liste un hologramme portant le nom saisi dans le champ
- Un bouton « Supprimer le dernier » retire le dernier élément de la liste
- Affiche un compteur : « X actifs sur Y au total »
5. Template Ref
Marque le conteneur de la liste avec ref (par exemple ref="listRef", et const listRef = ref(null) dans le script). Après l'ajout d'un hologramme, Vue met à jour le DOM un instant plus tard : fais donc défiler la liste dans nextTick (importé depuis vue) : nextTick(() => { listRef.value.scrollTop = listRef.value.scrollHeight }). Sans nextTick, tu défiles jusqu'à l'ancien bas de la liste et le nouvel élément reste sous le bord.
Dans la fonction qui ajoute un hologramme, cela donne :
1function addHologram() {
2 if (!newName.value.trim()) return
3 holograms.value.push({ id: Date.now(), name: newName.value, type: 'custom', active: true })
4 newName.value = ''
5 nextTick(() => {
6 listRef.value.scrollTop = listRef.value.scrollHeight
7 })
8}Le tableau holograms change immédiatement et seule la mise à jour de la page est différée : c'est pourquoi tu lis scrollHeight seulement dans la fonction passée à nextTick. Pour que le conteneur puisse défiler, donne-lui une hauteur limitée dans le CSS, par exemple max-height: 360px et overflow-y: auto.
Développe le squelette ci-dessous avec toutes les fonctionnalités requises !
Retiens : un bon panneau garde les données à un seul endroit, et le template se contente de les afficher et de transmettre les ordres de l'opérateur.
Code de cette leçon : App.vue
1<script setup>
2import { ref } from 'vue'
3
4// Panneau d'hologrammes du NOVA LAB - projet final du module 2
5const filter = ref('all')
6const newName = ref('')
7
8const holograms = ref([
9 { id: 1, name: 'Carte de Mars', type: 'navigation', active: true },
10 { id: 2, name: 'Schéma du réacteur', type: 'engineering', active: true },
11 { id: 3, name: 'Données atmosphériques', type: 'science', active: false },
12 { id: 4, name: 'Plan de l’habitat', type: 'architecture', active: true }
13])
14
15// TODO : Ajoute le filtrage des hologrammes selon le statut
16// TODO : Ajoute la bascule du statut des hologrammes
17// TODO : Ajoute l'ajout d'un nouvel hologramme
18// TODO : Utilise un template ref pour faire défiler la liste
19
20const filteredHolograms = ref(holograms.value)
21
22function toggleStatus(id) {
23 const h = holograms.value.find(h => h.id === id)
24 if (h) h.active = !h.active
25}
26
27function addHologram() {
28 if (!newName.value.trim()) return
29 holograms.value.push({
30 id: Date.now(),
31 name: newName.value,
32 type: 'custom',
33 active: true
34 })
35 newName.value = ''
36}
37</script>
38
39<template>
40 <div class="hologram-panel">
41 <h1>Panneau d'hologrammes du NOVA LAB</h1>
42
43 <div class="filters">
44 <button @click="filter = 'all'" :class="{ active: filter === 'all' }">Tous</button>
45 <button @click="filter = 'active'" :class="{ active: filter === 'active' }">Actifs</button>
46 <button @click="filter = 'inactive'" :class="{ active: filter === 'inactive' }">Inactifs</button>
47 </div>
48
49 <div class="add-form">
50 <input v-model="newName" placeholder="Nom du nouvel hologramme" @keyup.enter="addHologram" />
51 <button @click="addHologram">Ajouter</button>
52 </div>
53
54 <div class="hologram-list">
55 <!-- TODO : Utilise v-for avec les hologrammes filtrés -->
56 <!-- TODO : Ajoute des classes :class dynamiques -->
57 <div
58 v-for="h in holograms"
59 :key="h.id"
60 class="hologram-card"
61 :class="{ 'is-active': h.active, 'is-inactive': !h.active }"
62 @click="toggleStatus(h.id)"
63 >
64 <h3>{{ h.name }}</h3>
65 <span class="type">{{ h.type }}</span>
66 <span class="status">{{ h.active ? 'ACTIF' : 'INACTIF' }}</span>
67 </div>
68 </div>
69 </div>
70</template>
71
72<style scoped>
73.hologram-panel { background: #0d0d2b; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
74h1 { color: #7b68ee; text-align: center; }
75.filters { display: flex; gap: 0.5rem; margin: 1rem 0; justify-content: center; }
76.filters button { background: #1a1a4e; color: #aaa; border: 1px solid #333; padding: 0.5rem 1rem; cursor: pointer; border-radius: 4px; }
77.filters button.active { background: #7b68ee; color: white; }
78.add-form { display: flex; gap: 0.5rem; margin: 1rem 0; }
79.add-form input { flex: 1; background: #1a1a4e; border: 1px solid #333; color: white; padding: 0.5rem; border-radius: 4px; }
80.add-form button { background: #7b68ee; color: white; border: none; padding: 0.5rem 1rem; cursor: pointer; border-radius: 4px; }
81.hologram-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
82.hologram-card { padding: 1rem; border-radius: 8px; cursor: pointer; background: #1a1a4e; transition: all 0.3s; }
83.hologram-card.is-active { border: 2px solid #00ff88; }
84.hologram-card.is-inactive { border: 2px solid #ff4444; opacity: 0.7; }
85.type { color: #888; font-size: 0.8rem; display: block; }
86.status { font-weight: bold; margin-top: 0.5rem; display: block; }
87</style>Tu as repéré une erreur dans cette leçon ?