VueJS
VueJS est une librairie javascript qui permet de construire des UI web de façon réactive et déclarative.
Comme ReactJS et Angular, il encourage la création et l’utilisation de composants sur le modèle des éléments HTML.
Creation d’un premier projet VueJS avec Vite et TailwindCSS
Installer Vite, VueJS, TailwindCSS
Configurer TailwindCSS
Remplacer tailwind.config.js :
Remplacer src/style.css :
Tester
Remplacer src/App.vue :
App.css peut être supprimé
Exécution dans le serveur de développement :
index.html
exécutesrc/main.js
: l’exécution decreateApp(App).mount('#app')
rend dynamique le template<div id="app">...</div>
deindex.html
count
est réactif : quand sa valeur change, les parties du DOM qui le référencent (comme{{ count }}
) changent aussi en temps-réeldans le contenu texte des éléments du template, on peut utiliser
{{ <expr> }}
dans l’attribut d’un élément, on peut écrire
:<attr>="<expr>"
Les expressions peuvent être quelconques, multiples (séparées par des ;
) et référencer des variables réactives.
Binding
Contenu d’un élement
Attribut d’un élément
Double binding pour <input>, <textarea>
<input v-model="name">
est du sucre syntaxique pour :
Propriétés calculées (computed properties)
Une computed property est un objet réactif dont la valeur est synchronisée sur la valeur d’autres objets réactifs par le biais d’une fonction.
Exemple :
- elles apportent beaucoup de déclarativité
- les propriétés calculées sont mises en cache selon leurs dépendances ; leur évaluation est paresseuse
Boucles et conditionnelles
La propriété key
est indispensable si des insertions / suppressions sont effectuées
Composants
- un composant est une vue paramétrable avec des propriétés
props
fournies par son composant père - un composant peut émettre des événements vers son composant père
- props down, events up : les propriétés descendent du père vers le fils, alors que des événements remontent du fils vers le père.
- l’héritage des
props
ne va que du père vers les fils, il ne descend pas vers les petit-fils - un composant émet des événements que seul son père peut écouter (et pas son grand-père)
Le modèle de communication est donc simple et bien cloisonné, strictement entre un père et son fils.
Cycle de vie d’un composant
On peut créer des hooks
pour chacun de ces moments du cycle de vie, par exemple :
Playgroound
https://play.vuejs.org
Exemple complet : chat temps-réel
Back-end
Front-end VueJS
La configuration de développement de Vite permet d’éviter l’erreur CORS qui interviendrait parce que le serveur de développement frontend et le serveur backend tournent sur le même serveur localhost, mais sur des ports différents :
Routage des URL front-end
Avec un site web statique dont les pages sont générées côté serveur, lors d’un changement d’url, le navigateur envoie une requête au serveur et remplace la vue par le contenu fourni par le serveur.
Avec une application SPA, le code JS peut surveiller et intercepter les changements de window.location.href
et mettre à jour le DOM en fonction de règles fournies par un module appelé routeur.
Vue-router
Le routage des url par le client n’est pas assuré par VueJS lui-même, mais par un module tiers : vue-router
.
main.js
/src/App.vue
router/index.js
components/ComponentA.vue
components/ComponentB.vue
Algorithmique
- à chaque fois qu’un nouvel url est poussé (
router.push
, lien<router-link>
), ou modifié (router.go
), la table de routage est consultée et la première règle qui s’applique est prise en compte. Toutes les autres sont ignorées. - le tag
<router-view />
de plus haut niveau est remplacé par le composant spécifié par la règle active. Selon la valeur deprops
(booléen, objet, fonction), les props du composant sont affectées (voir plus loin) - si la route contient des
children
, le même algorithme est appelé récursivement pour peupler les<router-view />
emboités
Le plugin Vue
dans le navigateur permet de suivre les associations entre routes et instances de <router-view />
Passage des props
aux composants d’une route
Mode booléen
Indiquer props: true
passe au composant référencé par la route, tout l’objet route.params
en tant que props
. Par exemple :
Ici c’est l’objet { id, postid }
qui est passé comme props
au composant Post
associé à la route
Mode objet
Quand props
dans la définition de la route est un objet, c’est lui qui sera passé tel-quel au composant
Mode fonction
Quand props
dans la définition de la route est une fonction, c’est la valeur de cette fonction qui sera passée au composant. Cette fonction prend comme argument l’objet route
. Par exemple :
Exemple complet
/src/router/index.js
/src/use/useCities.js
/src/App.vue
/src/views/CityList.vue
/src/views/CityDetail.vue
Bonne pratique : normaliser les données
Activité à réaliser : gestion de tickets, part-1
Créer un projet ‘ticket’ et initialiser vuejs/tailwindcss dans un répertoire ‘frontend’.
Dans une branche ‘part-1’, réaliser un formulaire de saisie de ticket d’intervention :
- catégorie : choix parmi ‘Mobilier’, ‘Informatique’, ‘Autre’
- description : saisie d’un texte libre
- urgence : choix parmi ‘faible’, ‘normale’, ‘forte’
Lors de l’appui sur ‘Valider’, afficher sur la console les données saisies.
Activité à réaliser : gestion de tickets, part-2
Dans une branche ‘part-2’, ajouter une logique de validation au formulaire :
- l’email doit être correctement formé
- une catégorie doit avoir été choisie
- la description doit être non vide
- un niveau de priorité doit avoir été choisi
Le bouton ‘Valider’ doit être ‘disabled’ tant que le formulaire est invalide.
Activité à réaliser : gestion de tickets, part-3
Dans une branche ‘part-3’, ajouter une sauvegarde des données saisies dans une base de données :
- dans une répertoire ‘backend’, créer un projet npm Express/Prisma
- le schéma de la base de données est :
generator client {
provider = "prisma-client-js"
}
model Ticket {
id Int @default(autoincrement()) @id
created_at DateTime @default(now()) @db.Timestamptz(6)
handled_at DateTime?
done_at DateTime?
email String
category Category
description String
priority PriorityLevel
status Status @default(submitted)
answer String?
}
enum Category {
furniture
computer
other
}
enum PriorityLevel {
low
normal
high
}
enum Status {
submitted
ongoing
done
}
datasource db {
provider = "postgresql"
url = "postgresql://user:password@localhost:5432/tickets"
}
- créer la route GET /api/ticket qui renvoie la liste de tous les tickets
- créer la route POST /api/ticket qui créé un nouveau ticket
Activité à réaliser : gestion de tickets, part-4
Dans une branche ‘part-4’, ajouter un routeur pour gérer plusieurs pages :
- installer et configurer vue-router
- mettre la saisie d’un ticket sur la route /
- ajouter une page /recap/:id qui affiche le récapitulatif de la demande :id, et aller vers cette page à chaque création de ticket
- ajouter une page /tickets qui affiche la liste de tous les tickets et leur statut
Activité à réaliser : gestion de tickets, part-5
Dans une branche ‘part-5’, structurer l’application en composants :
- TicketCard, propriété ‘ticketId’, qui affiche dans un petit cadre rectangulaire les informations essentielles d’un ticket d’identifiant ‘ticketId’, sauf le texte de description
- TicketDetail, propriété ‘ticketId’ qui affiche toutes les informations du ticket
Modifier la route /tickets pour qu’elle affiche la liste des tickets sous forme d’une colonne de TicketCard à gauche, et que le clic sur un des TicketCard ‘ticketId’ affiche le détail TicketDetail dans l’espace restant à droite, associé à la route /tickets/:ticketId
Lorsqu’un ticket de la liste de gauche est cliqué, sa couleur de fond doit changer pour indiquer qu’il est sélectionné. Réciproquement, lorsque l’url /ticket/:ticketId est entrée, le ticket associé de la liste doit apparaître sélectionné.
Activité à réaliser : gestion de tickets, part-6
Dans une branche ‘part-6’, normaliser les données et les mettre dans un état central.
Créer un module use/useTickets.js qui contient toute la logique métier de gestion des tickets, et qui stocke les informations recueillies dans un cache partagé :
- créer un cache ‘id2ticket’ qui associe à chaque id le ticket associé
- exporter une fonction ‘asyncTicket(ticketId)’ qui renvoie une promesse de ticket. Si ticketId est dans le cache il est renvoyé ; sinon il est demandé au backend et la promesse est ensuite résolue
- ajouter dans ce module les accès HTTP au backend, en stockant immédiatement les données reçues dans le cache d’état
Activité à réaliser : gestion de tickets, part-7
Dans une branche ‘part-7’, persister l’état dans sessionStorage
En utilisant https://vueuse.org/core/useSessionStorage/, persister le cache id2ticket dans la clé ‘id2ticket’ de sessionStorage. Vérifier que le contenu du cache résiste au rechargement de la page
Activité à réaliser : gestion de tickets, part-8
Dans une branche ‘part-8’, mettre en place un filtrage sur la liste des tickets
- filtrage par catégories
- filtrage par priorité
Activité à réaliser : gestion de tickets, part-9
Dans une branche ‘part-9’, mettre en place une authentification et autorisation minimales.
- mettre en place une route frontend publique
/signin
qui demande la saisie d’un email et d’un mot de passe et qui appelle le backend avecPOST /api/auth { email, password }
. Pour simplifier le backend contiendra une liste d’utilisateurs en dur :
[
{ email: 'admin@mail.fr', admin: true, password: '2xaf67yret32' },
{ email: 'jc@mail.fr', admin: false, password: 'hjaf67yret32' },
...
]
Le backend renverra un ordre de stockage d’un cookie JWT nommé access_token
dont le payload contient les autorisations, 'read-list'='all', 'read-ticket'='all', 'write-ticket'='all'
pour un administrateur, 'read-list'='(email)', 'read-ticket'='(email)', 'write-ticket'='none'
pour les autres
- sécuriser les routes backend pour qu’elles prennent en compte les autorisation contenues dans le payload du cookie JWT transmis avec chaque requête, en filtrant les listes renvoyées, ou en renvoyant le status 403 selon les cas
- sécuriser les routes frontend pour afficher des messages d’interdiction, en fonction de la page demandée et du cookie d’authentification présent