Vue.js est un framework Javascript permettant de définir, entre autres, des interfaces client Web en mode SPA (Single Page Application). Ce type de fonctionnement définit une page unique dont les différents composants sont affichés ou masqués en fonction des interactions avec l'utilisateur, sans qu'il soit nécessaire de recharger la page.
Vue.js peut se programmer de différentes manières. La plus pratique au niveau codage est d'utiliser un langage de script dédié et une définition des composants dans plusieurs fichiers séparés mais qui nécessite une pré-compilation et donc des outils supplémentaires. La plus simple d'utilisation est d'importer dans une page HTML la libraire Javascript de Vue et d'utiliser ses fonctionnalités directement dans la page HTML. C'est cette deuxième option que nous allons utiliser dans ce TP.
Pour l'ajout direct de fonctionnalités Vue dans une page HTML, on peut utiliser deux API (deux syntaxes différentes) : Options ou Composition. La première est plus simple d'utilisation et la seconde plus adaptée à des pages complexes mais requiert plus de compréhension des détails du fonctionnement de Vue. Les exemples présentés ci-dessous seront écrits avec l'API Options. On peut indifféremment implémenter un même client avec l'une ou l'autre des API.
La version de Vue.js utilisée ici est la version 3.
Dans les exemples de cette section, nous allons interroger le serveur Tomcat qui fera une requête sur le serveur MongoDB pour récupérer l'ensemble des sportifs de votre base de données du TP précédent. Ensuite, ces sportifs seront affichés dans une page HTML en utilisant les données réactives de Vue.
Pour commencer, vous allez créer dans votre application Web Java, une nouvelle Servlet intitulée "AccesMongo" dont voici le code : AccesMongo.java. Modifiez la ligne 25 pour avoir la bonne URL du serveur MongoDB et la ligne 28 pour utiliser le bon nom de votre base de données.
Une des fonctionnalités principales de Vue est de pouvoir définir
des données dites réactives. Les données sont définies dans un
composant Vue, elles sont affichées dans le HTML de la page via une
syntaxe dite à moustaches : {{ maDonnee }}
. Ensuite,
quand du code Javascript modifie une donnée, elle est automatiquement
mise à jour dans le HTML. De manière symétrique, si des entrées d'un
formulaire sont liées à des données du composant, quand l'utilisateur
modifie des champs, les données du composant sont modifiées en
conséquence.
Téléchargez le fichier sportifs-vue.html. Modifiez la ligne 51 pour changer le nom du projet de votre application Web (ici "TestWeb") de façon à ce que la requête HTTP corresponde bien à votre Servlet "AccesMongo". Ensuite, vous avez simplement à ouvrir le fichier dans un navigateur Web et la liste des sportifs doit s'afficher dans le navigateur.
Voici l'image du code de la page avec les numéros de ligne :
Explications sur le code :
main
est définie avec
l'identifiant "app". C'est cet identifiant qui permet de faire le
lien entre le code HTML et le code Vue.created
est un hook du cycle de vie d'un
composant. A différents moments du cycle de vie d'un composant, il
est possible d'associer des actions comme ici quand le composant est
créé. Le cycle de vie d'un composant Vue avec ses hooks est
détaillé sur cette page
:
https://fr.vuejs.org/guide/essentials/lifecycle.html
{{ auteur
}}
href
. Pour dire que le contenu de la balise
est une donnée du composant Vue, on utilise la
directive v-bind
qui donnerait
ici v-bind:href="siteWeb"
. Pour alléger cette syntaxe,
on peut simplement mettre un ":" devant l'attribut de la balise, ce
qui donne :href="siteWeb"
.nbSportifs
. Ce n'est pas une donnée du
composant mais une valeur calculée qui s'utilise de la même
façon. Ce calcul est défini à la ligne 56 dans la
section computed
du composant Vue. Ces valeurs
calculées permettent de récupérer le résultat d'expressions
Javascript qui peuvent être complexes sans définir explicitement une
donnée et sans mettre ce code Javascript dans le HTML
directement. Ici, on retourne simplement la longueur du tableau de sportifs.
Notez l'utilisation de this
pour accéder à la
donnée "sportifs". Dans le code du script du composant Vue,
le this
permet de préciser que l'on accède à une
donnée du composant (définie dans la
partie data()
), sinon par défaut c'est une variable
du code Javascript courant.
fetch
dans la section "created()"
qui est exécuté une fois que la page a été créée. Cet appel récupère
un contenu qui est converti/traité en JSON (ligne 52) qui sert à
initialiser le contenu de la donnée "sportifs" (ligne 53).
div
à partir de la ligne 13 va parcourir
le tableau de sportifs et comme dans l'exemple de la page JSP/JSTL
vue en cours, on va combiner des balises HTML avec des balises Vue
(la différence principale est que ces balises sont exécutées coté
client alors que le JSTL est exécuté coté serveur pour fabriquer du
code HTML). La directive v-for
permet de faire une
boucle, v-if
, v-else-if
et v-else
permettent de réaliser des actions
conditionnelles. Ces instructions Vue sont directement intégrées
dans les balises HTML et seront dynamiquement remplacées par le code
HTML résultant de l'application des instructions comme avec
JSTL.div
intègre une boucle
traitant le tableau de sportifs : à chaque élément du tableau, une
balise div
spécifique à cet élément sera dynamiquement
créée dans le HTML. La syntaxe est la suivante pour "sp in sportifs"
: "sportifs" est le tableau à parcourir (c'est une donnée du
composant Vue) et "sp" sera l'élément courant du tableau. Par
convention, on doit définir une clé qui est unique pour chaque
élément du tableau avec la syntaxe :key
: ici, chaque
sportif possède déjà un identifiant unique nommé "_id" que l'on va
donc utiliser comme clé.div
, sinon on affichera le contenu de
l'autre div
avec le v-else
ligne 26.li
pour chaque élément du tableau "disciplines"
du sportif courant. La syntaxe est différente de précédemment car il
n'y a pas de clé explicite via les éléments du tableau. La syntaxe
en "(disc, cle) in sp.disciplines" se comprend comme : l'élément
courant du tableau "disciplines" du sportif courant est mis dans la
variable "disc" et "cle" sera la clé générée automatiquement pour
cet élément (concrètement, c'est sa position dans le tableau). On
retrouve ensuite la variable "cle" dans la définition de la clé
avec :key
.@click
: quand on clique sur le bouton, la
méthode est appelée en passant l'identifiant du sportif
("sp._id"). En effet, comme il y a plusieurs sportifs affichés sur
la page, il faut savoir quel est celui dont on veut modifier l'âge :
l'identifiant permet d'identifier ce sportif de manière unique. Le
deuxième paramètre $event
est facultatif : il désigne
l'événement au sens Javascript et peut être utile dans certains cas
(on peut par exemple récupérer la balise HTML du bouton directement
par ce biais).C'est ici que l'on voit clairement le coté réactif des données : dès que l'on modifie l'age d'un sportif de la donnée "sportifs" du composant Vue, à chaque endroit où il est affiché dans le HTML via la syntaxe à moustaches, la valeur est automatiquement modifiée dans le HTML affiché.
La page formulaire-vue.html fait
le lien entre les données d'un composant Vue et les entrées d'un
formulaire (listes, zones de texte ...) en utilisant la
directive v-model
. Voici le code de la page HTML :
select
est créée par les lignes 12 et 16. On
utilise la directive v-for
pour rajouter chaque sportif
dans la liste (avec :value="sp"
) mais en n'affichant que
le prénom et le nom du sportif dans les items de la liste HTML. La
ligne 12 contient v-model="leSportif"
dans la définition
de la balise select
. Cela exprime simplement que la
valeur sélectionnée dans la liste (qui est un sportif) sera placée
dans la donnée "leSportif" du composant Vue.div
contient un formulaire avec
une directive v-show
s'appliquant sur la donnée
"selectionne". v-show
affiche ou masque l'élément HTML
selon la valeur booléenne associée. Initialement, la donnée
"selectionne" vaut faux (ligne 33) et donc le div
n'est
pas affiché. On veut afficher le formulaire seulement si un sportif
est sélectionné dans la liste.watch
. Quand cette variable est modifiée
(concrètement quand on sélectionne un sportif dans la liste), le code
associé est exécuté. Ce code passe la donnée "selectionne" à vrai et
en conséquence, le div
du formulaire s'affiche
désormais. L'observation d'une donnée se fait en récupérant la
nouvelle et l'ancienne valeurs de la donnée comme on le voit ligne 39
avec les paramètres "nouvelleValeur" et "ancienneValeur" (ici il n'est
pas utile de traiter ces informations mais simplement de savoir que la
donnée a été modifiée).
v-model
pour préremplir les champs (lignes 20 à
24) avec le contenu de la donnée "leSportif". Si l'on modifie une
valeur, cela est répercuté dans le tableau de sportifs affiché dans la
liste et contenu dans la donnée "sportifs".Pensez à modifier la ligne 46 pour avoir la bonne URL de la requête sur le serveur Tomcat, comme pour l'exemple précédent.
Dans les deux exemples précédents, il y a un seul composant Vue par page, que l'on appelle composant racine. Vue permet d'instancier plusieurs composants dans la même page en plus du composant racine. Ces composants sont similaires à ceux déjà vus avec en plus un template HTML : quand le composant sera instancié, il affichera un bout de code HTML. L'usage le plus pratique est de définir chaque composant dans un fichier spécifique, en suivant le modèle SFC (Single-File Component). La limite est qu'il faut ensuite compiler les composants pour former une seule page et cela nécessite des outils spécifiques. Ici nous allons suivre notre logique d'utilisation directe de Vue sans outil et donc définir tous les composants dans un seul fichier HTML.
Une fois le composant créé, on l'utilisera comme une nouvelle
balise HTML qui à chaque usage instanciera un composant. On aura alors
la syntaxe suivante : <mon-composant propriete="..."
@unevenement="...">contenu
falcultatif</mon-composant>
. Un composant définit
plusieurs éléments :
Téléchargez le fichier composants-vue.html. Cette page va afficher la liste des sportifs comme dans le premier exemple mais cette fois en utilisant une instance de composant dédiée par sportif. Modifiez comme précédemment la ligne 32 pour avoir le bon lien vers la Servlet de votre application Web.
Le composant racine est le même que dans le premier exemple, il s'appelle "ChargerSportif" et est défini à partir de la ligne 23. Il définit une donnée "sportifs" qui va contenir tous les sportifs de la base de données via une requête sur le serveur Tomcat. Il définit une donnée "monRoger" qui est un sportif défini en dur et une donnée "leSelectionne" qui contiendra le nom du dernier sportif sélectionné.
La balise main
au début de la page contient le code
HTML principal de la page :
affichersportif
. Elle correspond à l'instanciation du
composant d'affichage d'un sportif. Ligne 14, on utilise une
directive v-for
pour parcourir l'ensemble des sportifs :
à chaque sportif de la liste, une instance dédiée du composant est
créée.:lesportif="sp"
.
@selectionne="afficherSelectionne"
. Les événements permettent
au composant fils (ici affichersportif
) d'envoyer des
informations au composant parent. Quand l'événement "selectionne" est
généré par le composant fils, la méthode "afficherSelectionne" est
appelée sur le composant parent. Cette méthode est définie ligne 37
dans le code du composant "ChargerSportif" qui est associé à la
balise main
de la page :Voici le code du composant "AfficherSportif" qui est instancié en
plusieurs exemplaires aux lignes 14 et 15 du code HTML principal de la
page :
$emit
. Il y a deux paramètres associés à
l'événement (le prénom et le nom du sportif). Les paramètres sont
optionnels. On retrouve leur traitement dans une méthode du composant
racine "ChargerSportif" à la ligne 37.
H3
contenant l'identité du sportif, la méthode
"selectNom" quand on clique sur le titre. C'est cette méthode qui
enverra l'événement "selectionne" avec l'identitié du sportif au
composant parent et qui sera affiché en vert ligne 13 via la donnée
"leSelectionne" qui a été mise à jour par le traitement de l'événement
dans le composant parent "ChargerSportif" via la méthode
"afficherSelectionne".
La définition de ce template n'est pas très
pratique car on doit mettre le code HTML dans une chaine définie par
des quotes inversés. Cela rend le code plus difficile à écrire et à
maintenir car les éditeurs de code le traitent comme une chaine et non
pas comme un contenu HTML. Cette limite est uniquement due au fait
que l'on définit ici tous nos composants dans un seul fichier pour
éviter une compilation. Si l'on suit le mode SCF, on aura un
composant par fichier et là on définira une balise
HTML <template> ...</template>
contenant le
code HTML du composant écrit de manière classique et plus pratique à
maintenir.
<slot></slot>
. Cette balise va être
remplacée par le code HTML qui a été passé entre les balises du
composant comme en ligne 16 où un paragraphe avec un texte en orange
est précisé entre la balise ouvrante et fermante du composant. Il est
possible d'avoir plusieurs slots dans le template : dans ce cas il
faut nommer les slots pour les différencier. Avec les propriétés qui
permettent de passer des données au composant, les slots sont une
autre manière de paramétrer le composant en lui passant du HTML à
intégrer dans son template.Enfin, pour lancer l'application, il faut associer les composants
avec leurs balises comme ceci :
affichersportif
est associée au
composant "AfficherSportif" (défini ligne 44). Si vous voulez
formatter autrement le nom de la balise, sachez que par défaut,
l'écriture se fait en mode kebab case et non pas camel
case ou Pascal case. Concrètement, la balise pourrait
être afficher-sportif
(et
pas AfficherSportif
sauf dans un autre usage des
composants que le mode fichier unique avec l'API Options).main
ligne 11 ayant l'identifiant "app".Les exemples ci-dessus présentent les principales fonctionnalités de Vue mais ce framework permet de faire beaucoup d'autres choses.
Nous n'avons par exemple pas abordé la mise en forme dynamique avec la possibilité de changer facilement par des directives Vue le style CSS d'éléments HTML.
Nous avons vu la communication basique entre composants : un parent peut passer des paramètres à un enfant et un enfant peut envoyer un événement à son parent. Mais il peut être utile que des composants qui ne sont pas directement parent/enfant puissent s'envoyer des événements. Pour cela, il faudra utiliser des "routeurs". On peut cependant facilement envoyer des données d'un parent vers sa descendance plus profonde que le premier enfant via les directives "Provide / Inject".
Pour l'identification des éléments HTML dans la page, la
directive ref
est plus pratique que d'utiliser
l'identifiant HTML id
. On peut mettre un identifiant de
la même façon comme par exemple <div ref="monDiv">
et ensuite, on peut facilement récupérer la référence de l'élément
dans une méthode du composant via this.$refs.monDiv
Enfin, comme précisé ci-dessus, nous avons ici utilisé l'écriture de composants Vue au sein d'une seule page HTML qui offre l'avantage de ne nécessiter aucune installation mais rend le code plus difficile à maintenir. Il existe des outils qui permettent de définir un projet Vue avec des fichiers bien séparés que l'on compilera ensuite ensemble, comme par exemple Vite ou Vue CLI.
Au niveau outil, l'éditeur dont sont tirés les screenshots de code ci-dessus est Visual Studio Code avec l'extension Volar.
Pour aller plus loin dans l'étude des fonctionnalités de Vue, voici quelques liens :
Téléchargez les 3 exemples ci-dessus, testez les et prenez le temps de comprendre leur fonctionnement.
Ensuite, modifiez ou créez des pages HTML utilisant Vue pour accéder aux données de vos bases en envoyant des requêtes sur le serveur Tomcat.
Dans les exemples, la commande Javascript fetch
est
utilisée pour faire une requête HTTP sur le serveur Tomcat. Elle est
plus simple à utiliser que les requêtes AJAX
avec XMLHttpRequest. Vous trouverez sur cette page une documentation
sur cette commande pour savoir notamment comment rajouter des
paramètres à la requête envoyée au serveur HTTP
:
https://developer.mozilla.org/fr/docs/Web/API/Fetch_API/Using_Fetch.
Si vous n'arrivez pas à faire fonctionner ou si vous n'avez pas
fini la partie d'accès à MongoBD via le serveur Tomcat,
le fetch
ne chargera rien dans la données "sportifs" des
3 exemples et vos pages n'afficheront rien ou presque. Dans ce cas,
supprimez dans les 3 exemples, les 4 lignes de code du
champ created()
et dans la section data
,
remplissez en dur le contenu de "sportifs" comme ceci :
data() {
return {
sportifs: [{"_id": 1, "prenom": "Roger", "nom": "Blanchard", "age": 57, "genre": "homme", "adresse": {"rue": "12 rue de Siam", "ville": "Brest", "codePostal": 29200}, "disciplines": ["100m", "200m", "descente"], "marie": true},{"_id": 2, "prenom": "Simone", "nom": "Blanchard", "age": 52, "genre": "femme", "adresse": {"rue": "12 rue de Siam", "ville": "Brest", "codePostal": 29200}, "disciplines": ["200m 4 nages", "100m libre"], "marie": true},{"_id": 3, "prenom": "Gérard", "nom": "Lebreton", "age": 24, "genre": "homme", "adresse": {"rue": "20 rue Saint-Michel", "ville": "Rennes", "codePostal": 35000}, "disciplines": ["marathon", "slalom", "géant", "100m libre", "descente", "100m"], "lastModified": {"$date": "2023-01-21T21:32:35.774Z"}},{"_id": 4, "prenom": "Régine", "nom": "Mouvier", "age": 41, "genre": "femme", "adresse": {"rue": "153 rue Jean Jaures", "ville": "Brest", "codePostal": 29200}, "disciplines": ["descente", "100m libre", "marathon", "marathon", "géant"]},{"_id": 12, "nom": "Legrand", "prenom": "Saturnin", "age": 23, "genre": "homme", "adresse": {"rue": "12 rue de Navarre", "ville": "Pau", "codePostal": 64000}}],
...
Eric Cariou, dernière modification : 12/02/23