Préambule
- Ce TP vient compléter le TP 1 en utilisant les directives basiques de vuejs pour modifier le comportement des 3 composants existants.
- Il n'y pas besoin de créer un nouveau projet : il suffit de travailler sur les sources créées dans le TP 1.
- Certaines parties sont "à recopier" et servent d'exemples pour les exercices réels.
1°/ Affichage de listes avec v-for.
- Si vous avez correctement implémenté l'exercice 3.4 du TP1, vous devriez avoir dans le template de BankAccountView.vue une ligne du style :
<p>passed transactions: {{ accountTransactions }}</p>
- Cette ligne affiche le tableau des transactions reliés à un numéro de compte, tel que présent dans le store, c'est-à-dire au format JSON. Cela suppose que vous avez utilisé une variable accountTransactions dans le store (comme suggéré dans le sujet)
- Au lieu d'afficher bêtement l'objet JSON, il est possible de créer une liste à puce grâce à <ul> + <li>.
- Pour créer autant de balise <li> qu'il y a des personnages, il suffit d'utiliser la directive v-for de vuejs.
Exercice :
- Utilisez cette directive dans BankAccountView pour afficher une liste à puce, avec comme information le montant et la date de la transaction
Remarque : dans data.js, la date stockée dans le tableau transactions n'est pas directement une chaîne de caractère normalisée, mais telle que stockée dans le BdD, c.a.d. sous la forme d'un objet avec un champ $date, qui lui est normalisé. La vraie date est donc accessible grâce à une expression du type trans.date.$date. De plus, le format normalisé n'est pas très lisible (par ex : 2023-05-30T17:09:29.199Z) donc il vaut mieux le transformer pour un humain.
Exercice :
- transformez la date de chaque transaction pour qu'elle soit affichée au format : "XX/YY/ZZZZ at HH:MM:SS", où XX est le mois, YY le jour, et ZZZZ l'année.
Pour ce faire, vous avez plusieurs solutions, la plus "simple" consistant à créer une fonction de transformation dans methods, et de l'appeler dans le template.
2°/ Champ de saisie et v-model
- La balise <input> permet de créer des champs de saisie de type texte, case à cocher, bouton radio.
- Pour que l'état du champ soit relié de façon bidirectionnelle à une variable d'un composant, il suffit d'utiliser la directive v-model en lui assignant le nom de la variable.
- De ce fait, si on modifie la valeur de la variable, le changement sera reporté dans le champ et inversement, si l'utilisateur change l'état du champ grâce à une interaction souris/clavier, la valeur de la variable sera modifiée en conséquence.
A faire dans VirusesView :
- Dans la partie <script>, modifiez le contenu de l'attribut data (rappel : il contient les variables locales du composant) comme suivant :
data: () => ({
priceFilter: 0,
}),
- Cela permet d'ajouter 1 variable locale au composant, qui est observées par vuejs.
- Dans la partie <script>, modifiez le contenu de l'attribut computed comme suivant :
computed: {
...mapState(['viruses']),
filterVirusesByPrice() {
if (this.priceFilter >0 ) return this.viruses.filter(v => v.price < this.priceFilter)
return this.viruses
}
}
- Cela permet d'ajouter une variable calculée nommée filterVirusesByPrice. La valeur de cette variable est un tableau créé en filtrant le tableau viruses grâce à la valeur de la variable priceFilter.
- Selon les principes des variables calculées, dès que priceFilter change, la méthode computed filterVirusesByPrice() est appelée, ce qui permet de recalculer la valeur de filterVirusesByPrice.
- Dans la partie <template>, modifiez comme suivant :
<label for="filterprice">prix inférieur à : </label><input v-model="priceFilter" id="filterprice">
<ul>
<li v-for="(virus, index) in filterVirusesByPrice" :key="index">{{virus.nom}} : {{virus.price}}</li>
</ul>
- On remarque que v-for opère maintenant sur filterVirusesByPrice.
- Conformément aux principes d'observation et de v-model, si l'utilisateur tape un nombre dans le champ de saisie, priceFilter prend comme valeur ce texte, ce qui provoque le recalcul de filterVirusesByPrice.
- Malheureusement, le champ de saisie contient du texte et pas une valeur numérique. Or, filterVirusesByPrice() fait une comparaison numérique, ce qui peut provoquer des comportements non désirés, selon ce que saisi l'utilisateur
Exercice 1 :
- Trouvez une façon soit de saisir directement une valeur numérique, soit de transformer la valeur saisie en nombre. Mais si le résultat de la conversion n'est pas un nombre, on affiche aucun item.
Exercice 2 :
- Utilisez le même principe pour mettre en place un filtrage basé sur le nom d'un virus, via un champ de saisie texte, par exemple en ajoutant une variable booléenne nameFilter dans data, et une méthode computed filterVirusesByName()
- Les virus qui contiennent la chaîne, même partiellement, doivent être affichés dans une deuxième liste à puce, en dessous de celle avec le filtre sur le prix.
Exercice 3 :
- Utilisez le même principe pour mettre en place un filtrage basé sur le fait qu'un virus soit en stock, via une case à cocher, par exemple en ajoutant une variable booléenne stockFilter dans data, et une méthode computed filterVirusesByStock()
- La liste obtenue doit être affichée sous la 2ème, sous la forme d'une table de X+1 lignes et 2 colonnes, X étant la taille de la liste. La première ligne de la table contient "Nom" et "Prix", les lignes suivantes contiennent le nom et le prix des virus de la liste.
3°/ Rendu conditionnel avec v-if
- Très souvent, une partie du template d'un composant ne doit être affiché que sous certaines conditions.
- Par exemple, les filtres mis en place dans VirusesView pourraient n'être visibles que si l'utilisateur le désire.
- Il faudrait donc ajouter des cases à cocher pour "activer" l'apparition du filtre.
A faire dans VirusesView :
- Dans data, ajoutez une variable locale nommée filterPriceActive, initialisée à false.
- Dans la partie template, modifiez comme suivant :
<span>Filtres :</span><label for="filterpriceactive">par prix</label><input type="checkbox" v-model="filterPriceActive" id="filterpriceactive">
<hr />
<div v-if="filterPriceActive">
<label for="filterprice">prix inférieur à : </label><input v-model="priceFilter" id="filterprice">
</div>
- On remarque que le champ de filtre apparaît ou disparaît en fonction du fait que la case soit cochée ou non.
- En effet, la balise <div> est affichée en fonction de la valeur de filterPriceActive, qui est elle-même mise à jour en (dé)cochant la case.
- Problème fonctionnel : si on décoche la case et qu'un filtre est en place, la liste est toujours filtrée !
Exercice 1 :
- Utilisez le même principe pour rendre (in)actif le filtre sur les noms
Exercice 2 :
- Utilisez le même principe pour rendre (in)actif le filtre sur le stock
Rq : il y a un problème fonctionnel avec ces cases à cocher. Si on les décoche et qu'un filtre est en place, les listes apparaissent toujours filtrées, ce qui n'est pas normal
Exercice 3 :
- Modifiez VirusesView le moins possible afin de régler le problème mentionné ci-dessous : si les cases sont décochées, les listes doivent contenir tous les virus.
4°/ Pour aller plus loin
4.1°/ Filtrage multi-critères
A faire dans VirusesView :
- modifier le composant pour qu'il n'y ait plus qu'une seul liste affichée mais filtrée en fonction des critères choisis via les 3 filtres possibles.
Il existe plusieurs solutions pour résoudre ce problème, plus ou moins efficaces. La plus "simple" consiste à écrire une nouvelle méthode computed qui fait des filtrages successifs, en fonction des filtres actifs.
4.2°/ rendre cohérent l'affichage dans BankAccountView
Si l'utilisateur ne tape rien dans le champ de saisie, il peut quand même cliquer sur les boutons et ainsi demander au store de récupérer des informations. D'après le code des services, la réponse va être une erreur. Ce comportement n'est pas désirable et il faudrait invalider les boutons si le champ de saisie est vide.
A faire dans BankAccountView :
- modifier le template pour que les 2 boutons soient inactifs quand le champ de saisie est vide.
- Dans une application professionnelle, il faudrait aller plus loin en vérifiant que le numéro saisi a le bon format.
- Dans cette application, les numéros de comptes sont au format : 22 lettres/chiffres, suivies d'un -, et enfin 7 chiffres.
A faire dans BankAccountView :
- modifier le template pour que les 2 boutons soient inactifs quand le champ de saisie ne contient pas de numéro au bon format
- modifier la partie script pour vérifier automatiquement si le numéro a le bon format (par ex, via une méthode computed)
Rq : plus besoin de vérifier si le champ est vide
Dans BankAccountView, si l'utilisateur tape un numéro de compte invalide et clique sur un des 2 boutons, il ne se passe rien, à part dans la console. Or, si l'utilisateur avait tapé auparavant un numéro valide, les informations restent affichées, ce qui n'est pas normal.
Pour résoudre ce problème, il faudrait soit afficher les informations du compte (solde ou transactions) si le numéro est bon, soit un message du style "compte invalide" si le numéro n'est pas bon. Malheureusement, l'information comme quoi le numéro est invalide est uniquement connu dans les actions du store, qui récupèrent un objet réponse avec un champ error > 0. La seule façon pour un composant de savoir si son accès à une action du store a provoqué une erreur ou non est donc que le store lui-même stocke un état d'erreur par rapport à la demande.
A faire dans le store :
- ajouter dans le state une variable qui indique l'état d'erreur sur le numéro de compte, par exemple accountNumberError. On l'initialise à 0 (= pas d'erreur car pas de numéro de compte renseigné)
- ajouter une mutation qui permet de mettre à jour la valeur de cette variable.
- dans l'action getAccountAmount(), s'il y a une erreur, mettre -1 dans accountNumberError, et sinon mettre 1.
- faire de même dans getAccountTransactions()
A faire dans BankAccountView :
- modifier le template pour :
- ajouter un bouton "reset" qui efface le contenu du champ de saisie du numéro de compte
- n'afficher le solde du compte et les transactions que si accountNumerError = 1. S'il vaut 0, on affiche rien et s'il vaut -1, on affiche un texte du type "numéro de compte erroné"
- modifier la partie script pour :
- mapper la variable accountNumberError, ainsi que la mutation qui met à jour cette variable.
- ajouter une méthode qui met chaîne vide dans la variable locale number et qui met accountNumberError à 0.
Rq : si on clique sur le bouton "Reset", les 2 autres boutons devraient automatiquement être invalidés.
Enfin, la route qui permet d'accéder à la age est /bank/amount. Compte tenu de ce qu'elle permet d'afficher, il vadurait mieux utiliser une route /bank/account.
A faire dans le router :
- modifier la route permettant d'afficher BankAccountView pour qu'elle soit /bank/account