Préambule
 
  • 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.

 

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.

 
Démonstration :
  • Copier le fichier TD4Demo1.vue dans TD4Demo.vue
  • lancer npm run serve puis visualiser le résultat dans le navigateur (par ex. http://localhost:8080)
  • 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 value.
  • 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.

 

Démonstration :
  • Copier le fichier TD4Demo2.vue dans TD4Demo.vue
  • lancer npm run serve puis visualiser le résultat dans le navigateur (par ex. http://localhost:8080)
  • 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 el 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 incVal => le champ de saisie est forcé de se réactualiser.

 

3°/ Émettre un événement

  • 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)
  • Pour capturer cet événement dans le composant parent, 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.

 

Démonstration :
  • Copier le fichier TD4Demo3.vue dans TD4Demo.vue
  • lancer npm run serve puis visualiser le résultat dans le navigateur (par ex. http://localhost:8080)
  • 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 value et incVal (NB : on pourrait bien entendu renommer les champs, par exemple { myval : value, myinc: incVal} )
  • Montrer le code de App.vue. On constate que l'on capture l'événement myEvent et que l'on appelle avec l'objet $event en paramètre. Dans le cas présent, c'est l'objet qui est émis grâce à $emit().
  • 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().

 
4°/ Relation avec les props
 
  • 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.
Démonstration :
  • Copier le fichier TD4Demo4.vue dans TD4Demo.vue
  • lancer npm run serve puis visualiser le résultat dans le navigateur (par ex. http://localhost:8080)
  • 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.

 

5°/ Quid d'une redescente des événements ?
 
  • 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é.