L'archive des sources (répertoire src) pour les démonstrations est téléchargeable [ ici ].

 1°/ Objectifs de Vuetify

  • Les cours précédents se sont focalisés sur l'aspect fonctionnel d'une application SPA avec vuejs.
  • Il est temps d'aborder le côté visuel.
  • Vuetify est simplement un des plugins les plus aboutis pour créer l'interface graphique d'une application.
  • Il propose des composants plus ou moins complexes (en terme de fonctionnalités) pour construire une application avec un design orienté Material.
  • Pour autant, vuetify a 2 problèmes essentiels :
    • sa richesse : on a parfois du mal à voir à quoi peut servir tel ou tel composant. C'est là que l'aide d'un graphiste est la bienvenue :-)
    • sa documentation exhaustive mais assez peu explicative (cf. le site de vuetify  : https://vuetifyjs.com/)
      • Le descriptif des composants consiste uniquement en des exemples de codes, souvent longs et dont il faut comprendre la mécanique soi même.
      • L'API des composants liste leurs différentes propriétés mais sans faire le lien avec les exemples de code, et parfois sans aucune explications sur comment les utiliser, voire à quoi elles servent.
  • A ces 2 problèmes s'ajoute le côté verbeux du code (cf. les exemples du site) qui rend rapidement les templates des composants peu lisibles.
  • Fort heureusement, ces problèmes disparaissent assez rapidement à partir du moment où l'on a compris les mécanismes de bases d'utilisation de vuetify, car ils sont relativement cohérents pour tous les composants.
  • Ce TD est l'occasion de découvrir ces mécanismes généraux et de les illustrer avec des exemples pratiques "courts".

2°/ Utilisation de Vuetify dans un projet vue-cli

  • Pour intégrer vuetify dans un projet créé grâce à vue-cli, il suffit de :
    • créer son projet avec vue-cli,
    • d'aller dans le répertoire du projet,
    • de lancer la commande : vue add vuetify
  • Certains fichier vont être modifiés, notamment App.vue qui va être complètement réécrit et contenir des balises vuetify.
  • le fichier main.js est également modifié afin d'importer vuetify et de l'intégrer à l'instance de vue.
  • Après cela, on peux faire le ménage et effacer les composant inutiles.

3°/ Principes fondamentaux de Vuetify

3.1°/ les composants vuetify.

  • Vuetify propose essentiellement des composants graphiques, mais aussi quelques uns purement fonctionnels (par ex, un chargeur de composant dynamique)
  • Cela va de la simple icône à des tables triables/paginables, en passant par les classiques : boutons, champ de saisie, cases à cocher, ...
  • Comme tout composant vuejs, il suffit d'écrire une balise portant le nom du composant pour l'utiliser dans un template.

Exemple, avec un bouton vuetify :

<template>
  <div>
    <v-btn>Clic</v-btn>
  </div>
</template>
  • En vuejs, tout composant possède 2 props que l'on a jamais besoin de déclarer : style et class.
  • Ces 2 props permettent de modifier l'apparence graphique du composant grâce aux valeurs qui seront données à style et class, c'est-à-dire des instructions CSS ou des classes de style CSS.
  • Si on veut modifier l'apparence du bouton de l'exemple ci-dessus, il suffit donc de relier des valeurs aux attributs style et/ou class de la balise <v-btn>

Exemple, texte en vert et padding à gauche :

<template>
  <div>
    <v-btn :style="{color:'green', paddingLeft: '50px'}">Clic</v-btn>
  </div>
</template>

 

  • Cependant, avec vuetify, on continue d'utiliser ces 2 attributs mais avec des classes CSS prédéfinies, dans le style de bootstrap (NB: cela dit, rien n'empêche d'utiliser et/ou ajouter ses propres styles).

Exemple, marges de 40 pixels et padding de 8 pixels :

<template>
  <div>
    <v-btn
      class="ma-10 pa-2"
    >
      Clic
    </v-btn>
  </div>
</template>
  • Cependant, les composant vuetify ont d'autres props permettant de configurer leur apparence et leur comportement, de façon bien plus pratique qu'avec du CSS.
  • Elles peuvent également avoir des slots, permettant par exemple de modifier leur contenu, soit celui par défaut, soit celui lors de certains événements, etc.
  • Certains de ces slots sont des scoped-slots, ce qui permet de faire les modifications en utilisant les variables qui sont "exportées" par le slot.
  • Enfin, certaines émettent des signaux que l'on peut capturer avec v-on.

3.2°/ La grille de positionnement

  • Généralement, on ne se contente pas d'écrire des balises dans un template en comptant sur le navigateur pour les organiser à l'écran.
  • On essaie de fixer leur position et taille plus ou moins précisément.
  • Vuetify propose un système de positionnement et de redimensionnement automatique des composants, basé sur le système flexbox et donc très proche de celui de bootstrap.
  • Il est responsive et permet d'adapter l'IG à différents supports (mobile, tablette, ordi, ...)
  • Le principe de base est de mettre les composants que l'on veut afficher dans un conteneur, représenté par la balise <v-container>
  • Ensuite, on définit des lignes de composants, grâce à <v-row>. Les lignes auront la largeur maximale du conteneur, qui va varier selon la taille de la fenêtre et des autres conteneurs. Par défaut, la hauteur de ces lignes dépend des composants qui vont s'y trouver.
  • Pour chaque ligne, on utilise <v-col> pour créer une colonne dans laquelle est affiché un composant.
  • Par défaut, chaque colonne aura une taille équivalente, mais il est possible de spécifier une sorte de taille grâce à la props cols, avec une valeur allant de 1 à 12.
  • Sur une même ligne, la somme de ces tailles ne doit pas dépasser 12 si on ne veut pas avoir des éléments qui "vont à la ligne".
  • Qui plus est, il est possible de définir différentes tailles selon la taille du support, grâce aux props sm, md, lg et xl.

Exemple, 1 seule ligne et 2 colonnes, par défaut de rapport de taille 1/5, et de rapport 1/2 pour les supports md et au-delà :

<v-container>
  <v-row>
    <v-col cols="2" md="4">
      <v-btn block> Bouton 1 </v-btn>
    </v-col>
    <v-col cols="10" md="8">
      <v-btn block> Bouton 2 </v-btn>
    </v-col>
  </v-row>
<v-container>

Démonstration n°1 :

  • copier App.vue.1 dans App.vue
  • npm run serve, pour lancer la démo.
  • faire varier la taille du naviguateur : à 960px de large, on change de rapport de taille.
  • modifier le code et mettre md="10" pour la 2ème colonne : la somme dépasse 12 et cette colonne est "mise à la ligne"
  • modifier le code et mettre md="6" pour la 2ème colonne : la somme est inférieure à 12 donc pas de problème, excepté que le bouton n'atteint pas la droite du conteneur.

4°/ exemples

4.1°/ customiser un bouton

  • D'après l'API vuetify, la balise <v-btn> possède pas moins de 48 props, 2 slots et un signal.
  • Parmi les props on peut noter les suivantes :
    • color : modifie la couleur de fond (ou l'entourage),
    • rounded : bord gauche/droit arrondis,
    • outlined : bouton sans fond mais entouré,
    • block : prend toute la largeur disponible,
    • to : la route à suivre (définie grâce à vue-router) quand on clique sur le bouton,
    • loading : permet de lancer une animation du type chargement quand on clique sur le bouton.
  • Les 2 slots sont :
    • default : pour spécifier le contenu (texte, icon, ...) du bouton
    • loader : pour spécifier une autre animation que celle par défaut.
  • Le signal est click, qui est généré quand on clique sur le bouton.

Démonstration n°2 :

  • copier App.vue.2 dans App.vue
  • npm run serve, pour lancer la démo.
  • cliquer sur le bouton 2 : l'animation par défaut de chargement se lance (NB : pendant 3s grâce à la fonction doLoad() )
  • pour le bouton 1, changer pa-10 en pa-2 : le padding change.
  • pour le bouton 1, supprimer outlined : le style change

Remarques :

  • Les paddings et marges sont bien appliqués grâce aux noms spéciaux de classes de style.
  • Par exemple, le padding pt-5 du bouton 2 s'applique uniquement au haut du bouton (d'où le t après le p).
  • Le style du bouton change également en fonction des attributs "spéciaux" que l'on ajoute/enlève : rounded, block, ...
  • Le contenu du bouton est bien fixé par slot par défaut (i.e. ce que l'on met entre l'ouverture et la fermeture de <v-btn>).
  • On capture l'événement click comme avec n'importe quel composant vuejs : v-on:click, ou bien @click.

4.2°/ Dialog & Cards

  • Cet exemple permet d'illustrer comment certains composants vuetify utilisent les scoped-slots pour permettre à d'autres composants de déclencher des fonctions définies dans les premiers.
  • En l'occurrence, on va utiliser <v-dialog> qui permet d'ouvrir une boîte de dialogue quand on clique sur un élément, dénommé activateur dans la suite.
  • Cet élément peut être très divers : bouton, texte, icone, ... Pour autant, quel que soit son type, il faut qu'un clic dessus appelle la même fonction, nommé click(), qui est définie dans le composant <v-dialog> et qui permet d'afficher le dialogue.
  • Pour arriver à ce résultat, <v-dialog> fourni un scoped-slot nommé activator, qui "exporte" différents objets.
  • L'un de ces objets, nommé on, contient différentes fonctions, notamment la fonction click(), mais également d'autres permettant de gérer d'autres événements.
  • Pour que l'activateur puisse appeler cette fonction, il suffit de relier l'événement click à l'appel de la fonction on.click(), par exemple : @click="on.click".
  • Cependant, on contient d'autres fonctions, qu'il est important de relier aux événements correspondants.
  • Pour faire simple, on utilise une syntaxe spéciale de v-on pour relier toutes les fonctions de on d'un seul coup : v-on="on".
  • A part on, le scoped-slot activator exporte d'autres variables, attrs (= infos sur le rôle) et value (= état d'activation), que l'on peut fournir à l'activateur, si on en a besoin.
  • Au final, si on veut activer un dialogue à partir d'un bouton, on écrit :
<v-dialog>
  <template v-slot:activator={on, attrs, value}">
    <v-btn v-on="on" v-bind="attrs"> click <v-btn>
  </template>
  ... définition de la fenêtre de dialogue
</v-dialog>
  •  Pour définir la fenêtre de dialogue, il existe plusieurs solutions, mais la plus courante est d'utiliser une <v-card> et ses 3 "sous-balises" <v-card-title>, <v-card-text> et <v-card-actions>.
  • On crée ainsi un panneau dans lequel on met d'autres composants représentant le titre, le texte et les actions du dialogue.

Démonstration n°3 :

  • copier App.vue.3 dans App.vue
  • npm run serve, pour lancer la démo.
  • cliquer sur le bouton : la fenêtre de dialogue s'ouvre.
  • cliquer en dehors du dialogue : il reste ouvert. C'est l'attribut persistent qui permet cela.
  • cliquer sur oui ou non pour fermer le dialogue.

Remarques :

  • Le fait que la fenêtre de dialogue soit visible ou non est commandée par la valeur de l'attribut value de <v-dialog>.
  • On le voit bien dans le texte du bouton qui affiche justement la valeur de value, récupérée grâce au scoped-slot activator.
  • Si on utilise un activateur, la fonction click() appelée change value pour la mettre à true.
  • Pour fermer le dialogue, il faut donc remettre value à false.
  • Or, une fois dans la dialogue, on a pas un accès direct à cette variable.
  • Le plus simple est de relier value de façon bidirectionnelle (grâce à v-model) à une variable que l'on va mettre à false pour fermer le dialogue (cf. showDial dans le code).

 4.3°/ Table de données

  • Une des balises fournissant les fonctionnalités les plus complexes est <v-data-table>
  • Elle permet d'afficher des données sous forme de tableau, mais avec des possibilités natives de tris, filtrage, recherche, pagination, regroupement, ...
  • Le modèle de données qui sert à alimenter le tableau est très simple : on définit 2 tableaux, contentant des objets représentant les colonnes et les lignes du tableau.
    • Pour les colonnes, les objets doivent avoir au minimum une propriété value représentant le nom "abstrait" de la colonne, et une propriété text représentant le nom de la colonne affiché à l'écran.
    • Pour les lignes, les objets doivent avoir au minimum des propriétés correspondant au nom "abstrait" de chaque colonne. On peut cependant avoir d'autres propriétés, qui ne seront pas utilisées pour l'affichage.
  • Les colonnes peuvent également avoir des propriétés qui influent sur les fonctionnalités associées à <v-data-table>. Par exemple :
    • groupable : si false, le tableau ne peut pas être affiché en groupant les éléments de la colonne par valeur identique.
    • sortable : si false, la colonne ne peut pas être triée.
    • filterable: si false, le mécanisme de recherche ne prend pas en compte la colonne
  • Ces 2 tableaux doivent être accessibles dans le composant qui utilise <v-data-table>. On peut donc les mettre dans sa propriété data, ou bien dans un store si les données sont utilisées par d'autres composants.
  • Il suffit ensuite de relier les attributs headers et items à ces 2 tableaux dans <v-data-table>

Exemple basique, avec un tableau de nom/prénom, seule la colonne nom peut être triée :

<template>
  <v-data-table
    :headers="titres"
    :items="infos"
    ...
  >
</template>
<script>
  name: 'App',
  data: () => ({
    titres: [
      {text: 'Nom', value: 'lastName'},
      {text: 'Prenom', value: 'firstName'}
    ],
    infos: [
      {id:0, lastName:'Dupond', firstName:'jean'},
      {id:1, lastName:'Durand', firstName:'alcyonne', sortable: false},
    ]
  }), 
  ...
}
</script>
      

 

  • Par défaut, le résultat d'affichage contient une ligne avec les titres de colonne, 5 lignes de données, le bandeau de pagination.
  • Pour pouvoir profiter du mécanisme de recherche, il faut donc créer un champ de saisie à part, par exemple grâce à <v-text-field> et relier son contenu à une variable du notre composant.
  • Cette même variable peut être reliée à l'attribut search de <v-data-table>.
  • Dès que la variable est modifiée, le mécanisme de recherche est exécuté et seuls les lignes correspondantes sont affichées.
  • Une correspondance existe pour une ligne à partir du moment où l'un des champs contient la suite de caractères donnée dans la variable.

Exemple :

<template>
  <v-text-field
    v-model="mysearch"
    label="chercher"
  ></v-text-field>
  <v-data-table
    :headers="titres"
    :items="infos"
    :search="mysearch"
  ></v-data-table>
</template>
<script>
export default {
  name: 'App',
  data: () => ({
     mysearch: '',
    ...
  }
}
  • Il existe bien d'autres fonctionnalités et personnalisations possibles, décrites sur le site de vuetify. Par exemple, on peut rendre cliquable certains items afin de modifier leur valeur, via un dialogue. On peut personnaliser la fonction de recherche, etc.
  • La démonstration suivante présente celles abordées ci-dessus, plus la sélection de lignes.

Démonstration n°4 :

  • copier App.vue.4 dans App.vue
  • npm run serve, pour lancer la démo.
  • cliquer sur les boutons de pagination pour parcourir les pages.
  • cliquer sur "Tous" dans "lignes par pages" et réduire la hauteur de la fenêtre : le contenu du tableau n'est pas scrollable, c'est la fenêtre toute entière qui l'est.
  • cliquer une première fois sur la flèche qui apparaît à côté de Name : les données sont triées par ordre croissant sur Name.
  • cliquer une deuxième fois : les données sont triées par ordre décroissant.
  • cliquer une troisième fois : les données sont dans l'ordre de définition du code.
  • cliquer sur "group" dans la colonne "Course" : les lignes sont regroupées en prenant les valeurs identiques de matières, puis ces groupes sont classés par ordre alphabétique. cliquer sur une des croix pour enlever le regroupement.
  • taper 13 dans le champ de recherche : aucune correspondance trouvée. Normal car la colonne des notes n'est pas incluse dans la recherche.
  • taper nr dans le champ de recherche : seules les lignes contenant Henri sont affichées.
  • taper dd dans le champ de recherche : seules les lignes contenant BdD sont affichées.
  • cliquer sur quelques cases à cocher : la moyenne en bas change dynamiquement. En effet, on calcule cette moyenne dès que la variable selected change.
  • changer la ligne item-key="id" en item-key="name", puis sélectionner une case avec "Jean" : toutes les cases Jean sont sélectionnées en même temps. On peut donc influer sur le principe de sélection grâce à item-key.

Remarques :

  • La dernière manipulation implique que si l'on veut absolument pouvoir sélectionner des lignes indépendamment les unes des autres, il faut que item-key référence une propriété dont la valeur est unique pour chaque ligne. Il faut donc prévoir cela dans le modèle de données, notamment quand les données proviennent d'une BdD et qu'elles ne sont pas référencées par un id unique (NB : si la BdD est correctement conçue, cela ne devrait pas être la cas ! ).

4.4°/ Barre de navigation, Suivre des routes & divers bonus

  • Dans la plupart des applications, le haut de la page est constitué par une barre comportant par exemple un titre, une image, des boutons et/ou onglets, ...
  • Dans vuetify, on utilise <v-app-bar>, ses "sous-balises" ( <v-app-bar-title>, <v-app-bar-nav-icon>), ses props et slots pour créer cette barre de façon hautement personnalisable.
  • Parmi les props "remarquables", on note :
    • app : permet à la barre de modifier son apparence (notamment la taille), en fonction du contenu principal donné dans <v-main>, de la taille du navigateur, ...
    • src : pour mettre une image de fond,
    • hide-on-scroll : la barre est masquée dès que l'on scroll vers le bas,
    • height : pour fixer sa hauteur.
  • Généralement, cette barre contient des boutons/onglets servant à naviguer dans l'application.
  • Si on utilise <v-app-bar-nav-icon>, on profite du click sur l'icône pour afficher un dialogue, ou un panneau glissant, grâce à <v-navigation-drawer>.

 

  • Pour peupler le panneau glissant, on utilise généralement une liste d'item. Selon les besoins applicatifs, les items permettent de suivre des routes, d'être sélectionnés, de faire apparaître diverses infos, ...
  • Ce type d'élément est montré plus en détail dans la section suivante.

 

  • Quand les widgets existants sont insuffisants pour créer l'application, il faut parfois directement dessiner.
  • Pour cela, on utilise la balise html <canvas>
  • Pour dessiner sur un canvas dans un composant vuejs, le principe est relativement simple :
    • on définit dans la partie template un canvas avec un id
    • dans la méthode mounted() de la partie script, on récupère l'élément du DOM représentant le canvas, via getElementById().
    • grâce à cet élément, on crée un "contexte 2D" de dessin, que l'on affecte à une variable de data.
    • on utilise cette variable et les fonction de dessin pour remplir le canvas, par exemple dans les méthodes du composant.

Démonstration n°5 :

  • copier App.vue.5 dans App.vue
  • npm run serve, pour lancer la démo.
  • cliquer sur les boutons de en haut à droite pour afficher l'un ou l'autre des composants.
  • cliquer sur l'icône à gauche du titre : le panneau glissant apparaît, qui permet de suivre les mêmes routes que les 2 boutons.
  • dans ComponentA, on peut cliquer sur les boutons + et - pour augmenter/réduire la largeur de la zone rouge.
  • scroller vers le bas : la barre du haut est immédiatement masquée.
  • si on affiche ComponentB puis de nouveau ComponentA, la zone rouge fait toujours la même taille : normal car sa taille est stockée dans le store Vuex.

Remarques :

  • Il n'y a pas besoin de fermer explicitement le panneau glissant. En revanche, on doit l'ouvrir en mettant la valeur true dans son attribut value. Pour cela, on fait comme avec les dialogue, on utilise une liaison bidirectionnelle avec une variable que l'on peut mettre à true, en l'occurrence quand on clique sur l'icône du panneau.
  • Dans le fichier views/ComponentA.vue, on voit comment on peut "dessiner" dans un composant, grâce à la balise html <canvas>.
  • Pour cela, il suffit de récupérer lors du "montage" du composant, l'objet dans le DOM représentant l'élément canvas que l'on veut manipuler. 
  • ATTENTION : tous les navigateurs ne supportent pas cette balise et/ou toutes les fonctions de dessin possible. Mais avec les derniers navigateurs, on peut carrément récupérer un contexte de dessin 3D et faire de l'openGL.

 

4.5°/ Groupes d'éléments

  • Il est relativement fréquent que des éléments aient une relation, par exemple le fait qu'un seul soit actif à la fois, ou bien qu'ils soient affichés au même endroit.
  • Pour ces cas, vuetify propose différentes balises, selon le type d'élément à regrouper. Malheureusement, le nommage de ces balises n'est pas totalement cohérent :
    • pour les boutons : <v-btn-toggle>
    • pour les chips : <v-chip-group>
    • pour les items ou listes d'items : <v-item-group> et <v-list-item-group>
    • pour un emplacement unique d'affichage multi-contenu : <v-window>
    • pour des emplacements d'affichage multiples avec pagination : <v-slide-group> 
  • La façon de modifier le look des éléments en fonction de s'ils sont sélectionnés ou non dépend également de la balise utilisée. Le descriptif est donné ci-dessous.
  • En revanche, ils ont une base commune de fonctionnement : on peut relier une variable grâce à v-model pour savoir quels éléments sont sélectionnés.

Pour les groupes de boutons :

  • on utilise <v-btn-toggle> avec à l'intérieur des balises <v-btn>,
  • pour autoriser la sélection multiple, il suffit d'utiliser la props multiple de <v-btn-toggle>
  • si on veut modifier le look par défaut d'un bouton sélectionné, on utilise sa props active-class.

Pour les groupes de chips :

  • comportement et possibilités similaires aux boutons, mais avec des balises <v-chip> à l'intérieur de <v-chip-group>

Pour les groupes d'items :

  • comportement et possibilités similaires aux boutons, mais avec des balises <v-item> à l'intérieur de <v-item-group>,
  • de plus, il faut utiliser le scoped-slot par défaut de <v-item> qui permet de récupérer 2 propriétés : active et toggle. active permet de savoir si l'item est actif et donne la possibilité de modifier le contenu en fonction. toggle est une fonction qui doit être appelée lorsque l'on clique sur l'item, ce qui permet de mettre en place le mécanisme de sélection.
  • attention, si on utilise active-class dans <v-item-group> ET <v-item>, seul celle de l'item est prise en compte.

 

Pour les groupes de liste d'items :

  • Une liste d'éléments se crée avec <v-list>. Il suffit de mettre à l'intérieur une <v-list-item-group> avec dedans des <v-list-item>. De base, <v-list> permet d'obtenir une suite d'élément listés verticalement, et le fait de grouper ajoute les mécanismes de sélection.
  • Un <v-list-item> peut être défini grâce à des sous-balises permettant entre autre de définir un icon <v-list-item-icon>, un contenu <v-list-item-content>, ...
  • contrairement à <v-item>, on n'est pas obligé d'utiliser le scoped-slot par défaut. Cependant, si on veut changer le contenu d'un item en fonction de sa sélection, il faut alors utiliser le scoped-slot et récupérer la propriété active (toggle ne sert généralement pas)

Pour un emplacement multi-contenu :

  • on utilise <v-window> avec à l'intérieur des balises <v-window-item> qui définissent chaque contenu,
  • le fait de modifier la variable reliée avec v-model va faire passer d'un contenu à l'autre.

Pour un multi-contenu avec pagination :

  • on utilise <v-slide-group> avec à l'intérieur des balises <v-slide-item> qui définissent chaque contenu,
  • par défaut, les items sont affichés sur une ligne, qui peut être tronquée si la fenêtre n'est pas assez grande.
  • pour faire apparaître les chevrons de pagination de chaque côté, il faut utiliser la props show-arrows. NB: on peut customiser le look des chevrons.
  • comme pour les groupes d'items, on doit utiliser le scoped-slot par défaut pour récupérer les propriétés active et toggle.

 

Démonstration n°6 :

  • copier App.vue.6 dans App.vue
  • npm run serve, pour lancer la démo.
  • explorer le code de App.vue avec les différents type de groupes.
  • pour les derniers types, le scoped-slot par défaut est utilisé avec la notation raccourcie de vuejs, donc sans utiliser la balise template, comme fait dans les groupes d'items. Ce raccourci n'est utilisable que lorsque qu'il n'existe qu'un seul slot, celui par défaut.