- Les démonstrations de ce TD se basent sur les sources disponibles [ ici ].
- Attention, seul le répertoire src est dans l'archive. Il faut donc créer un projet avec vue-cli et remplacer son répertoire src.
- Pour faciliter les démonstrations, un champ de saisie en haut de la page permet de saisir un n° de démonstration. Selon ce n°, le centre de la page change pour faire apparaît la démonstration associée.
1°/ Mettre en place un écouteur d'événement : v-on
- En JS basique, on met en place un écouteur d'événement sur une balise grâce aux attributs du type onXXX, par exemple onclick.
- La valeur de cet attribut est généralement du code javascript simple, par exemple un appel à une fonction qui va gérer l'événement.
- Vuejs propose la directive v-on pour obtenir le même résultat.
- La différence est que v-on est paramétrable par le type d'événement que l'on veut écouter et que sa valeur est une expression javascript (comme pour les moustaches, v-bind, ...) accédant directement aux données/méthodes (cf. ci-dessous) de l'instance de vue.
- La syntaxe est : v-on:nom_événement="expression JS"
- Comme pour v-bind, il existe un raccourci d'écriture : @nom_événement="expression JS"
Remarque : si on met en place une capture d'événement et que ce dernier n'est jamais généré, cela n'a aucun impact sur l'exécution.
- Lancer l'application : npm run dev et accéder à l'URL
- Taper 1 dans le champ de saisie du haut
- Montrer le code de TD4Demo1.vue. On constate que v-on capture l'événement clic sur le bouton et appelle doInc() qui modifie la valeur de val.
- Cliquer sur le bouton : la valeur affichée change automatiquement.
- Remplacer v-on: par un @ => ça fonctionne également
- Problème avec v-model : le champ de saisie est considéré comme du texte, même si on tape un nombre. Mettre A dans le champ de saisie et cliquer sur le bouton : un A est ajoutée après la valeur affichée. Normal, on a fait une concaténation de chaîne. La solution à ce problème est donnée dans la section suivante.
2°/ Récupérer l'événement
- Il est relativement fréquent que l'écouteur d'événement appelle une fonction prenant en paramètre une valeur tirée du composant ayant généré l'événement.
- Par exemple, lorsqu'un champ de saisie est validé (événement change), on veut récupérer la valeur de celui-ci pour la donner en paramètre à la fonction qui va traiter l'événement.
- Pour cela, il faut récupérer l'objet événement. Vuejs met à disposition une variable globale $event, référençant l'objet événement courant.
- Grâce à cette variable, on a notamment accès au composant cible de l'événement : $event.target.
- On peut ensuite accéder aux attributs, par exemple pour un champ de saisie : $event.target.value.
- Lancer l'application : npm run dev et accéder à l'URL
- Taper 2 dans le champ de saisie du haut
- Montrer le code de TD4Demo2.vue. On constate que l'on utilise pas v-model dans la champ de saisie, mais une capture de l'événement change, qui appelle la fonction checkInc() avec en paramètre la valeur du champ de saisie.
- Cliquer sur le bouton : la valeur affichée change automatiquement.
- Taper 3 dans le champ de saisie puis sur entrée : un message de confirmation s'affiche dans la console. L'incrémentation est maintenant de 3.
- Taper aaa dans le champ de saisie puis sur entrée : un message d'alerte s'affiche dans le console et rien ne se passe.
- Cliquer de nouveau sur le bouton : le champ de saisie remet la valeur initiale et l'incrémentation s'effectue avec cette valeur.
Problème : selon les actions, le champ de saisie va être actualisé ou non en cas de saisie incorrecte.
- Recharger la page et taper 3 dans le champ de saisie => ok.
- Taper bbb dans le champ de saisie puis cliquer directement sur le bouton => le champ de saisie est actualisé et affiche 3 puis l'incrémentation se fait.
- Taper 0 dans le champ de saisie puis cliquer sur le bouton => rien en change puisque l'incrémentation ne change pas la valeur.
- Taper aaa dans le champ de saisie puis cliquer sur le bouton => le champ de saisie ne réaffiche pas 0. Normal car rien n'est mis à jour dans la page.
Conclusion : il faut parfois "forcer" le réaffichage d'une variable qui n'a pas changé de valeur.
- solution simple : utiliser 2 instructions consécutives pour changer la valeur puis la remettre à l'initiale.
- Illustration : décommenter les deux lignes qui changent inc => le champ de saisie est forcé de se réactualiser.
3°/ Émettre un événement
- Dans le template, vuejs propose une fonction $emit() qui permet de générer explicitement un événement, avec un nom et une valeur fixés par le programmeur.
- Il est donc possible de créer de événements différents de ceux définis dans la norme HTML/JS.
- On peut utiliser cette fonction aussi bien dans la partie <template> que <script> (dans ce dernier cas, ne pas oublier de prefixer avec this.)
- La syntaxe est : $emit("nom_evenement", valeur)
- Dans la partie script, cette fonction n'est pas disponible et il faut déclarer explicitement les événements avec la fonction defineEmits().
- Cette fonction prend par défaut un tableau de chaîne de caractères en paramètre. Chaque chaîne représente un nom d'événement utilisé dans la partie script.
- Cette fonction retourne une fonction, que l'on peut nommer à volonté et qui sert d'équivalent à $emit(), avec les mêmes principes. Généralement, on nomme cette fonction emit.
Exemple :
<template>
...
<button @click="$emit('myevent', 1)">bouton 1</button>
<button @click="sendEvent">bouton 2</button>
...
</template>
<script setup>
...
const emit = defineEmits(["myevent"])
function sendEvent() {
emit("myevent", 2)
}
...
</script>
- Que le composant fils émette un événement depuis template ou script, la capture cet événement dans le composant parent se fait comme avec les événements natif : il suffit d'utiliser @nom_evenement.
ATTENTION :
- contrairement aux événements HTML classiques qui sont remontés de fils en père, jusqu'à atteindre l'objet windows, les événements créés avec vuejs ne sont remontés qu'au père du composant qui a émis l'événement.
- si cet événement doit remonter encore d'un niveau, il faut que le père réémette explicitement l'événement en utilisant lui aussi $emit.
- Lancer l'application : npm run dev et accéder à l'URL
- Taper 3 dans le champ de saisie du haut
- Montrer le code de TD4Demo3.vue. On constate que le clic sur le 2ème bouton provoque l'emission d'un événement myEvent, avec comme paramètre un objet contenant les valeurs de val et inc L'envoi de cet événement se fait via $emit() dans le template
- Quand on clique sur le 3ème bouton, cela provoque l'emission du même événement avec la même valeur mais l'envoi se fait via une fonciton se trouvant dans script.
- Montrer le code de App.vue. On constate que l'on capture l'événement myEvent et que l'on appelle une fonction doLog() avec l'objet $event en paramètre. Dans le cas présent, c'est l'objet qui est émis par TD4Demo3
- Incrémenter plusieurs fois la valeur puis cliquer sur le 2ème bouton. Dans la console, on voit apparaître le contenu de l'objet, signe que le composant parent a bien capturé l'événement.
Raccourci d'écriture : si l'on écrit <TD4Demo3 @myEvent="doLog" />, cela semble incorrect : ce n'est pas un appel à fonction, juste un nom de fonction. Pourtant, cela fonctionne très bien. En effet, dans ce cas, vuejs va comprendre qu'il faut appeler doLog() ET par défaut passer $event en paramètre. ATTENTION, cela ne fonctionne pas en écrivant doLog().
- On a vu qu'un composant fils n'a aucun moyen de modifier directement la valeur de ses props, car leur valeur est donnée par le composant parent.
- Cependant, il est relativement fréquent qu'une interaction utilisateur dans le fils implique un changement de la valeur de la props.
- Prenons par exemple, un composant fils générique qui affiche une liste d'item sélectionnables grâce à une case à cocher.
- Ce composant a comme props un tableau d'objets représentant les items de la liste, et un tableau de booléen indiquant quels items sont sélectionnés. Ces deux tableaux sont fournis par le composant père.
- Il va de soi que si l'utilisateur coche ou décoche une case, il faut mettre à jour ce tableau de booléen.
- Malheureusement, ce n'est pas possible directement.
- En revanche, c'est possible indirectement grâce aux événements : il suffit que le fils envoie à son père un événement indiquant quelle case a changé d'état.
- Le père capture l'événement et met à jour son tableau de booléen qu'il donne à son fils comme props. De ce fait, la props du fils est mise à jour.
- Lancer l'application : npm run dev et accéder à l'URL
- Taper 4 dans le champ de saisie du haut
- Montrer le code de TD4Demo4.vue. C'est le composant Selector qui permet d'afficher la liste avec cases à cocher. On remarque également que le tableau passé en props contient aussi bien le texte des items que s'ils sont sélectionnés. C'est plus simple que de passer 2 tableaux en props.
- Montrer le code de Selector.vue. Le principe pour faire remonter un changement de sélection d'un item est simple : on capture l'événement clic (comme dans les exemples précédents) et on utilise $emit pour envoyer au père l'indice de la case ou l'item cliqué. Cet indice est celui tiré du v-for utilisé pour afficher les items.
- On remarque dans TD4Demo4 que certains items sont déjà sélectionnés, ce qui correspond bien au fait que les cases sont cochées lors de l'affichage du composant.
Remarque : dans un navigateur, un clic sur une case change visuellement son état (sauf blocage explicite). Ce n'est donc pas une preuve que la props de Selector a été mise à jour. En revanche, quand on clique sur le texte d'un item, la case change, ce qui apporte la preuve voulue.
- Supposons 3 composants avec A le composant principal, B et C des composants fils.
- Comment faire pour qu'une action dans B implique une modification dans C ?
- Il serait bien pratique de pouvoir émettre des événements avec $emit d'un composant père vers un composant fils.
- Malheureusement, c'est impossible. Il est également impossible d’émettre un événement entre des composants "frères" ou "cousins".
- La seule solution viable avec les mécanismes de base de vuejs est d'utiliser le principe des props, abordés dans le TD 3.
- Pour donner l'idée principale :
- le composant A sert de point de stockage des données "communes" à B et C, ainsi que des méthodes permettant de modifier ces données.
- pour simplifier, on suppose que le composant B propose des actions sur ces données, alors que C affiche ces données.
- pour que C ait accès aux données, A va les partager sous la forme d'une props, c'est-à-dire d'une variable dans C qui va s'ajouter à celle de data et computed, mais dont la valeur est fournie par A.
- quand une action est déclenchée dans B, ce dernier émet un événement que A capture, puis il appelle une fonction modifiant les données.
- comme ces données sont partagées avec C, toute modification dans A va être répercutée dans C, donc son affichage va être réactualisé.