Préambule
L'objectif principal de ce TP est de reprendre le TP n°2 en manipulant les principes de props et d'événements, au travers de la création de nouveaux composants "outils"
1°/ La barre de navigation
1.1°/ Objectifs et contraintes
- Dans les TPs précédents, le changement d'affichage entre les composants VirusesView, ShopLoginView et BankAccountView est fait en changeant l'URL dans le navigateur.
- L'objectif de cet exercice est de créer une barre de navigation sous la forme d'un composant réutilisable.
- Pour le réaliser, il y a 2 options :
- soit c'est la barre de navigation qui permet elle-même de suivre une route,
- soit la barre émet un événement et le composant utilise la valeur de l'événement pour décider quoi faire, en l'occurrence quelle route prendre.
- L'avantage de la 2ème solution est qu'elle découple totalement la barre de navigation du fait que l'on utilise ou non vue-router. D'un point de vue composition de composants, c'est donc mieux.
1.2°/ Mise en place
- Créer dans src un sous répertoire components.
- Dans components, créer un composant NavBar.vue et copier/coller dedans le code suivant :
<template>
<div>
// button list, values/colors from props titles.
</div>
</template>
<script setup>
defineProps({
titles: Array
})
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
- La props titles (dont la valeur est fournie par le composant parent) est un tableau d'objet au format suivant :
[ {text: "menu1", color: "color1} , {text: "menu2", color: "color2}, ... ]
- Pour créer la liste de boutons dans la partie template, on utilise la balise <button> ... texte ... </button>
- Il doit y avoir autant de boutons qu'il y a d'objets dans la props titles et leur texte et couleur sont donnés par les champs text et color
- Quand on clique sur le bouton n° X, on doit émettre l'événement menu-clicked avec comme valeur X. Ainsi, le composant parent recevra la valeur 0 quand on clique sur le premier bouton, 1 sur le deuxième, etc.
1.3°/ Test
- Modifier App.vue :
- après la balise <div> du début de template, ajouter une instance du composant NavBar,
- donner en props à cette instance un objet adéquat, afin d'afficher 3 boutons, dont le texte est "Shop Login", "Viruses" et "Compte bancaire", la couleur étant libre.
- capturer l'événement menu-clicked et appeler une fonction (dans script) goTo(linkIndex), le paramètre étant la valeur de l'événement (donc 0, 1 ou 2 si vous avez correctement codé ce qui précède)
- cette fonction permet de suivre les 3 routes définies, afin de provoquer l'affichage soit du composant VirusesView, soit BankAccountView, soit ShopLoginView
Remarques :
- pour écrire la fonction goTo(), utilisez un des mécanismes fournis par vue-router : https://router.vuejs.org/guide/essentials/navigation.html
2°/ Une liste avec cases à cocher
2.1°/ objectifs et contraintes
- L'objectif est de créer un composant permettant d'afficher une liste verticale d'objets JSON de même type, mais en limitant l'affichage à certains champs de ces objets.
- Chaque item de la liste peut être (de façon optionnelle) :
- précédé par une case à cocher,
- suivie par un bouton.
- Enfin, la liste est suivie par un bouton optionnel.
- Pour cela, le composant doit avoir comme props :
- data : un tableau d'objets de même type,
- fields : un tableau donnant le nom des champs du tableau qu'il faut afficher,
- itemChecked : un booléen qui indique s'il faut une case à cocher devant les items,
- checked : un tableau de booléens qui indique si une case est cochée ou non,
- itemButton : un objet au format { show: true/false, text: '....' }. Le booléen indique s'il faut un bouton après les items, et les ... sont le texte de ces boutons.
- listButton : un objet au format { show: true/false, text: '....' }. Le booléen indique s'il faut un bouton après la liste, et les ... sont le texte de ce bouton.
- Par exemple, prenons le tableau JSON suivant :
[
{nom:"dupond", prenom:"jean",age:31},
{nom:"durand", prenom:"pierre",age:35}
]
- Si on donne ce tableau en entrée au composant, ainsi que le tableau [ 'prenom', 'nom' ] représentant le nom des champs à afficher, alors le composant doit afficher (+ si demandé les cases à cocher et boutons) :
jean dupond
pierre durant
Rappel javascript : on peut accéder aux champs d'un objet grâce à la notation obj['nom_champ']. Par exemple, si j'ai une variable e contenant le premier objet du tableau d'exemple ci-dessus, alors e['nom'] contient 'dupond'.
2.2°/ Mise en place
- Dans components, Créer un composant CheckedList avec dedans :
<template>
<div>
</div>
</template>
<script setup>
defineProps({
data: Array, // les données sources
fields: Array, // le tableau contenant le nom des champs
itemCheck: Boolean, // s'il y a des case à cocher
checked: Array, // le tableau des cases cochées
itemButton: Object, // l'objet pour les boutons d'items
listButton: Object, // l'objet pour le bouton de liste
})
</script>
<style scoped>
</style>
Modifier la partie <template> pour que :
- le composant affiche sous forme de liste verticale (à vous de choisir l'apparence : paragraphes, liste à puce, table, ...) les objets de la props data. Pour chaque objet, seuls ses propriétés indiquées dans la props fields doivent être affichées (NB : dans l'ordre d'apparition dans fields).
- si la props itemCheck vaut true, chaque ligne de la liste doit commencer par une case à cocher. La ième case doit être cochée si checked[i] vaut true.
- si la props itemButton.show vaut true, chaque ligne doit se terminer par un bouton, dont le contenu est indiqué par itemButton.text
- si la props listButton.show vaut true, un bouton doit apparaître après la liste, dont le contenu est indiqué par listButton.text
En cas d'interaction utilisateur avec les cases et les boutons :
- chaque fois que l'état d'une case est changé, le composant doit émettre un événement checked-changed, avec comme valeur l'indice de la case à cocher (c.a.d. celui de la ligne)
- chaque fois qu'un bouton de fin de ligne est pressé, le composant doit émettre un événement item-button-clicked, avec comme valeur l'indice du bouton (c.a.d. celui de la ligne)
- chaque fois que le bouton après la liste est pressé, le composant doit émettre un événement list-button-clicked.
2.3°/ Tests
- Modifier VirusesView pour que la liste des virus (filtrée si possible) disponibles à l'achat soit affichée grâce au composant CheckedList :
- pour chaque virus, une ligne de CheckedList doit afficher son nom et son prix,
- chaque ligne doit commencer par une case à cocher et se terminer par un bouton,
- la liste des lignes doit être suivi d'une bouton.
- Quand l'utilisateur clique sur un bouton de fin de ligne, une boîte de dialogue apparaît (cf. méthode alert() de JS) avec un message indiquant le nom de l'item, le nombre en stock et s'il est en vente.
- Quand l'utilisateur clique sur le bouton d'après liste, une boîte de dialogue apparaît avec un message indiquant le nom de tous les virus dont la case est cochée.
Exercice 1 :
- La liste filtrée des virus est un déjà un tableau d'objets. On peut donc utiliser ce tableau directement comme valeur de la props data de CheckedList
- La props checked doit avoir comme valeur un tableau de booléen, fourni par VirusesView. Le problème, c'est que le nombre de virus peut varier en fonction du filtrage. La taille de checked doit donc varier en fonction du nombre actuel de virus dans la liste.
- Pour régler ce premier problème, on peut générer checked grâce à une variable computed, qui sera recalculée chaque fois que la liste filtrée change. Encore faut-il savoir si chaque item de la liste est sélectionné ou non.
- Pour régler ce deuxième problème, il suffit de stocker les indices sélectionnés dans la liste complète des virus. Pour cela, on ajoute :
- une variable locale nommée selected, contenant au départ un tableau vide.
- une fonction changeSelection(idx) qui :
- récupère le virus à l'indice idx dans la liste filtrée,
- trouve à quel indice dans la liste complète se trouve ce virus,
- si cet indice n'est pas dans selected, elle l'ajoute dedans, et sinon elle le supprime.
- Dans l'instance de CheckedList, on capture l'événement checked-changed et on appelle changeSelection().
- Grâce à cette façon de faire, si une sélection existe et que l'on change le filtrage, la nouvelle liste fera apparaître les cases cochées au bon endroit car elles seront recalculées en fonction du contenu de la nouvelle liste.
Exercice 2 :
- La solution mise en place dans l'exercice précédent n'est cependant pas forcément satisfaisante.
- En effet, si l'on sélectionne variole et covid, que l'on active le filtrage par stock, la ligne avec variole disparaît et covid reste coché.
- Cependant, si on désactive le filtrage par stock, variole réapparaît MAIS il est toujours sélectionné ! C'est normal car son indice est toujours présent dans selected.
- Ce comportement peut être considéré comme valide, mais pour certaines fonctionnalités/applications, ce n'est pas le cas.
- L'exercice consiste à trouver une solution pour retirer automatiquement de selected les indices de virus qui ne sont pas dans la liste filtrée.