Préambule
 
  • Les démonstrations de ce TD se basent sur les sources disponibles [ ici ].
  • Attention, seul le répetoire src est dans l'archive. Il faut donc créer un projet avec vue-cli et remplacer son répertoire src.

 

1°/ Principes de vuejs

  • L'objectif de Vuejs est de prendre en charge l'affichage et le rafraîchissement automatique d'éléments d'une page HTML quand la valeur de variables JS change.
  • Pour cela, vuejs met en place des mécanismes d'observation de ces variables et déclenche la mise à jour du DOM quand leur valeur change.
  • Les valeurs de ces variables peuvent aussi bien venir d'un script JS qui est chargé localement dans le navigateur, que de requêtes à un serveur qui va répondre au navigateur avec par exemple des données au format JSON.
  • Le principe de création d'une application Web avec vuejs repose donc clairement sur le principe de séparation strict entre la visualisation des données et le contrôle des actions utilisateur, qui est entièrement géré par le navigateur, et la fourniture des données qui est gérée par des serveurs.
  • L'utilisation de vuejs nécessite de penser autrement le développement web, par rapport aux pratiques classiques liées au PHP/Python/... où le serveur interprète un langage pour produire du HTML.
  • Avec vuejs, le serveur Web se contente d'envoyer au navigateur un unique fichier html, des scripts JS, du css. Cet ensemble de fichiers peut être entièrement écrit "à la main", ou bien partiellement généré grâce à un environnement de développement tel que vue-cli.
  • Contrairement à l'approche php, les scripts JS exécutés par le navigateur contiennent toute la logique de fonctionnement de l'application Web.
  • Quand des données externes sont nécessaires, les scripts JS peuvent interroger un serveur de type API, pour aller les récupérer avec une requête asynchrone. Le navigateur n'a donc pas besoin de demander au serveur Web une autre page.
  • Cette approche est la même que pour une application client/serveur basée sur des sockets.
  • Son énorme avantage est de pouvoir développer la partie front-end (navigateur) et le back-end (serveurs) en parallèle.
  • La seule chose qui doit être réglée avant est de spécifier le format des requêtes et des données échangées entre le navigateur et les serveurs.
  • Pour créer et tester une application vuejs, il suffit d'avoir à sa disposition un éditeur de texte et un navigateur.
  • En effet, il n'y a pas spécialement besoin d'héberger les fichiers créés sur un serveur Web, à part quand on passe en phase de déploiement/production.
  • Pour autant, en milieu professionnel, la création d'applications web de type SPA (Single Page Application) avec Vuejs ne se fait pas en codant tout "à la main".
  • En effet, vuejs repose sur la définition de composants. Un composant contient généralement une partie décrivant son aspect visuel (HTML) et une autre partie décrivant son aspect fonctionnel (en JS).
  • Or, écrire des composants et les assembler sans passer par un environnement de dev. tel que vue-cli est relativement verbeux et fastidieux.
  • C'est pourquoi on utilise tout le temps ce type d'environnement, à part pour de très petits exemples pédagogiques (cf. article hello world)
  • Premièrement, vue-cli comporte des outils permettant de créer une application web à partir de fichiers dont la syntaxe n'est pas du pur javascript. Ce sont des fichiers dit SFC : Single File Components qui permettent de décrire des composants, à savoir leur visuel écrit en HTML + instructions vuejs, leurs données et fonctions écrit en JS, et leur sytle écrit en CSS. Leur description est donnée en section 2.
  • Il y a donc des logiciels associés à vue-cli permettant de faire la "compilation" et l'assemblage de ces fichiers pour produire une application.
  • L'organisation du code de l'application est modulaire, à savoir qu'un fichier constitue un module qui va "exporter" des variables/fonctions que d'autres fichiers (donc modules) vont pouvoir importer pour les manipuler. En terme de résultat, c'est un peu similaire au include du php.
  • Deuxièmement, vue-cli est un gestionnaire de projet :
    • on peut créer facilement un arborescence basique d'application,
    • on peut ajouter des modules/plugins au projet,
    • on peut tester rapidement l'application,
    • on peut créer (et tester) une version de production, prête à être déployée sur un serveur Web.
    • etc.
  • Enfin, vue-cli permet de profiter de composants écrits par d'autres développeurs.

 

2°/ Description d'un fichier .vue

2.1°/ Structuration générale

  • Un fichier .vue décrit un composant en 3 parties :
<template>
    <!-- visuel du composant, en HTML + directive vuejs -->
</template>

<script>
    // modèle des données et du contrôle du composant, écrit en JS
</script>

<style>
    // définition des styles CSS
</style>
  • Seule la partie <template> est obligatoire. Comme indiqué, cette partie est rédigée en HTML, plus les directives introduites par vuejs.
  • ATTENTION : <template> ne doit contenir qu'une seule balise "racine". Bien entendu, cette balise peut contenir elle-même d'autres balises. C'est pourquoi on utilise très souvent la balise <div> comme balise racine.

 

  • La partie <script> devient nécessaire lorsque la partie <template> doit afficher des données non statiques et sans utiliser les anciens principes de manipulation du DOM.
  • Dans ce cas, la partie <script> va décrire les données locales du composant, celles reçues du composant parent, ainsi que les traitements manipulant ces données.

 

 2.2°/ partie <script>

  • De façon générale, cette partie définit un objet exporté, avec des attributs et/ou des fonctions bien précis définis par vuejs.
  • Selon les besoins fonctionnels du composant, on va utiliser plus ou moins de ces attributs.
  • De plus, si cet objet a besoin de fonction/objets définis dans d'autres fichiers, on place avant sa définition toutes les importations nécessaires.

 

  • Le patron de code ci-dessous donne la plupart des attributs/fonctions possibles et leur contenu :
<script>
// importation de modules/fonctions/objets/...
export default {
  name: 'MyComponent', // le nom du composant
  components: { // liste des composants importés },
  data: () => {
    return {
      // déclaration des variables locales du composant
    }
  },
  props: {
    // definition des variables données par le composant parent
  },
  computed: {
    // définition des fonctions associées aux variables calculées
  },
  watch: {
    // définition des fonctions appelées automatiquement lors de changement de valeur de variables locales/props
  },
  methods: {
    //définition des fonctions "normales"
  },
  created() {
    // instructions appelées lors de l'instanciation du composant
  },
  mounted() {
    // instruction appelées lorsque le composant est intégré dans le DOM
  },
  updated() {
    // instructions appelées lorsque le composant est actualisé dans le DOM
  }
}
</script>

 

2.2.1°/ data

  • L'attribut data définit de façon "indirecte" les variables locales au composant : data doit être une fonction retournant un objet contenant les variables.
  • Généralement, on utilise la notation fléchée pour définir cette fonction.
  • Vuejs va "observer" ces variables et si elles sont utilisées dans la partie template et que leur valeur change, l'affichage va être automatiquement mis à jour. Si ces variables sont utilisées dans des fonctions définies dans les attributs computed et/ou watch, ces fonctions seront automatiquement appelées par vuejs en cas de changement de valeur.

VERY BIG WARNING ! L'observation par vuejs a quelques limites sur les objets et les tableaux :

  • si on ajoute des attributs à un objet après la création du composant qui définit cet objet, ces nouveaux attributs ne seront pas observés.
  • si on change directement la valeur d'un élément d'un tableau grâce à la notation [] (par ex, mytab[2] = 5), vuejs ne détectera pas le changement. C'est pourquoi il faut TOUJOURS utiliser :
    • push() pour ajouter des éléments à un tableau,
    • splice() pour supprimer/remplacer des éléments
  • NB : ce dernier point vaut également pour un tableau qui serait à l'intérieur d'un objet.

 

  • On peut utiliser 2 types de variables :
    • celles qui sont définies dans un fichier JS "externe" : dans ce cas, on met juste le nom de ces variables dans l'objet retourné, sans valeur. ATTENTION ! Ces variables ne sont utilisables ainsi que si l'on les a importées.
    • celles qui sont propres au composant : dans ce cas, on met un nom à cette variable et on lui donne une valeur par défaut.

2.2.2°/ props

  • L'attribut props définit les noms des variables dont la valeur est fournie par le composant parent.
  • Cela correspond à la situation d'un composant A, qui a dans son template une balise <B> ... </B>, où B est le nom d'un autre composant. Dans ce cas, A peut passer à B des valeurs comme props, grâce à la directive v-bind (cf. TD 2)
  • Comme pour les variables de data, Vuejs va observer ces "props" et en cas de changement, selon où elles sont utilisées, réactualiser le DOM, appeler les fonctions de computed et/ou watch.

 ATTENTION : on ne doit JAMAIS modifier directement la valeur d'une props, par exemple dans une fonction de watch ou methods.

 

2.2.3°/ computed

  • La valeur de l'attribut computed est un objet contenant uniquement des fonctions.
  • Chaque fonction a un nom, qui va correspondre à une nouvelle variable dite variable calculée propre au composant. Par exemple, si on crée une fonction total(), le composant aura une nouvelle variable calculée locale nommé total, en plus de celles définies dans data.
  • Chaque fonction permet de calculer la valeur de la variable qui lui est associée. Pour affecter la valeur, il suffit que la fonction renvoie celle-ci avec return.
  • Une variable calculée est elle-même observée. On a donc le même comportement de vuejs qu'avec les variables de data et les props.
  • A noter qu'une variable peut être calculée grâce à d'autres variables calculées.

ATTENTION : on ne doit JAMAIS modifier directement la valeur d'une variable calculée, par exemple dans une fonction de watch ou methods.

 

  • Il faut voir les variables calculées comme des sortes de super-getters, permettant d'obtenir "immédiatement" une et une seule valeur à partir de variables du composant.
  • Il ne faut donc surtout pas écrire une fonction computed pour, par exemple, aller chercher une valeur sur une API distante.
  • De même, une fonction computed ne doit JAMAIS modifier la valeur de variables de data, seulement les lire.

 

2.2.4°/ watch

  • La valeur de l'attribut watch est un objet contenant uniquement des fonctions (comme computed)
  • Le nom de ces fonctions (dites watcher) est obligatoirement celui d'une variable de data, calculée, ou props.
  • Les watchers n'ont pas pour vocation de retourner une valeur comme les fonctions computed. D'ailleurs une fonction watcher ne renvoie rien.
  • En revanche, on s'en sert si on a besoin de déclencher automatiquement des traitements lorsqu'une, et une seule variable (data, computed ou props) change de valeur.
  • On peut tout imaginer comme traitement : requête à une API, affichage d'alertes, appeler des fonctions définies dans methods, et même changer la valeur de variables de data (ce qui n'est pas possible avec les fonctions computed)
  • La signature des watcher est : nom_fonction( nouvelle_valeur, ancienne_valeur), le deuxième paramètre étant optionnel.

 

2.2.5°/ methods

  • La valeur de l'attribut methods est un objet contenant uniquement des fonctions (comme computed)
  • Contrairement à computed, et watch ces fonctions ne sont pas associées à une variable. Ce sont juste des fonctions "normales" qui permettent de gérer le composant.
  • De plus, ces fonctions doivent être appelées explicitement, par exemple lors d'une interaction de l'utilisateur avec l'IG, ou bien dans d'autres fonctions.

 

2.2.6°/ created(), mounted(), updated()

  • Par défaut, ces fonctions ne font rien et on peut les "redéfinir" dans les composants.
  • Il suffit de donner le code de ces fonctions pour que vuejs exécute ce code, respectivement lors de la création du composant, lors de son intégration dans le DOM, ou à chaque fois que le composant est actualisé.
  • Généralement, created() et mounted() sont utilisées pour initialiser des données dont le composant à besoin. Ces données peuvent être des variables de data, ou bien des données externes importées.
  • updated() est beaucoup plus rarement utilisé car un composant bien écrit doit normalement mettre à jour son visuel et état dynamiquement. Sauf exception, il n'y a donc aucun raison de faire des choses "en plus" grâce à updated().

 

2.2.7°/ manipulation 

  • Pour utiliser ces variables et fonctions :
    • dans la partie template, on utilise juste le nom de la variable ou de la fonction,
    • dans la partie script, on utilise this.nom_variable ou this.nom_fonction.
  • ATTENTION : on appelle JAMAIS une méthode computed, on manipule toujours la variable calculée qui porte le même nom.

 

3°/ Exemple illustratif (avec spoiler sur les directives vuejs)

 

ATTENTION ! Cet exemple permet juste d'illustrer les différents points abordés précédemment, dans un but pédagogique. En pratique, on n'écrirait pas un tel composant de cette façon, mais plus simplement (par ex, pas besoin de watcher, ni de mounted() ).


 Télécharger les sources : [ vuejs-td1-src.tgz ]

NB : cette archive contient uniquement le répertoire src. Il faut donc au préalable créer un projet basique avec vue-cli puis remplacer le répertoire src par celui contenu dans l'archive.


Dans model.js, on a :

var gamers = [ {name: 'jean', pseudo: 'jj'}, {name: 'pierre', pseudo: 'pepe'} ]
export {gamers}

 Dans MyComponent.vue, on a :

<template>
  <div>
    <h1>{{ title }}</h1>
    <p>Il y a actuellement {{ nbGamers }} joueurs </p>
    <select v-model="idSelected">
      <option v-for="(gamer,index) in gamers" :key="index" :value="index">{{gamer.name}}</option>
    </select>
    <p v-if="currentGamer">pseudo joueur sélectionné : {{ currentGamer.pseudo }} </p>
  </div>
</template>

<script>
import {gamers} from './model'
export default {
  name: 'MyComponent',
  props: {
    title: String
  },
  data: () => {
    return {
      gamers, // variable externe
      currentGamer : null, // variable interne
      idSelected: -1 // variable interne
    }
  },
  computed: {
    nbGamers() { return gamers.length } // si gamers change de taille, nbGamers sera automatiquement recaculé
  },
  methods: {
    setCurrentGamer(idx) { 
      if( (idx >= 0) && (idx < this.nbGamers) ) { // on accède à la variable locale calculée nbGamers
        this.currentGamer = this.gamers[idx] 
      }
    }
  },
  watch: {
    idSelected( newVal) {
      console.log("nouvelle sélection : "+newVal);
      this.setCurrentGamer(newVal);
    }
  },
  mounted() {
    this.currentGamer = null
    this.idSelected = -1
  }
}
</script>

 

Principes de fonctionnement : 
  • les items de la liste déroulante sont créés dynamiquement en fonction des objets se trouvant dans le tableau importé et observé nommé gamers.
  • grâce à la directive v-for, on peut parcourir les élément de ce tableau, en les comptant, pour créer des balises "en boucle".
  • dans le cas présent, le texte de chaque item est tiré de l'attribut name et la valeur correspondant à l'item sélectionné est l'indice dans le tableau.
  • grâce à la directive v-model, on indique à vuejs que la sélection de l'utilisateur doit mettre à jour la variable idSelected, qui prendra donc comme valeur un indice dans le tableau gamers.
  • grâce à un watcher, on observe tout changement de la valeur de idSelected. Si c'est le cas, on appelle une méthode qui met à jour un objet nommé currentGamer, représentant le joueur sélectionné.
  • on remarque également l'utilisation d'une variable calculée nbGamers pour contenir le nombre d'éléments dans gamers. Si gamers change, alors nbGamers sera réévaluée.
  • mounted() est utilisée pour initialiser les 2 variables locales lors de l'intégration du composant dans le DOM. (NB : c'est normalement inutile puisque l'on a donné la même valeur dans data).
  • enfin, on remarque l'utilisation d'une directive v-if, qui permet d'inclure de manière conditionnelle le paragraphe <p>