Installations
npm
Pour bénéficier de npm, il suffit d'installer Node.js (https://nodejs.org/fr/)
Vue CLI
L'installation de Vue CLI se fait par la commande :
npm install -g @vue/cli
Dans la partie précédente, nous avons travaillé en mode prototypage, c'est à dire en intégrant les scripts d'activation de Vue.js directement dans le code HTML.
Cependant, pour créer de (vraies) grosses applications single page, il est recommandé de travailler en mode projet avec npm (Node Package Manager de Node.js) et l'outil Vue CLI.
Pour bénéficier de npm, il suffit d'installer Node.js (https://nodejs.org/fr/)
L'installation de Vue CLI se fait par la commande :
npm install -g @vue/cli
Pour créer un projet avec Vue CLI, utilisez un shell et déplacez-vous dans le répertoire qui doit contenir
votre projet (ex. ~/fwf
).
Utilisez ensuite la commande vue create
:
vue create mon_tp
Pendant le processus de création, Vue CLI, vous demandera de choisir plusieurs options : vous pouvez dans un premier temps choisir toutes celles proposées par défaut.
Le template de projet a normalement été généré dans un dossier qui porte le nom du projet (avec un exemple de composant "HelloWorld"): il ne reste plus qu'à taper dedans :).
Pour lancer l'exécution en mode développement, il suffit de démarrer un serveur Node.js
dans le répertoire du projet.
Dans le dossier du projet (ex. ~/fwf/mon_projet
), utilisez la commande :
npm run serve
Si vous souhaitez utiliser WebStorm, il vous suffit maintenant de lui demander de créer un nouveau projet ("Empty Project") tout en sélectionnant le dossier qui a été généré par Vue CLI (le nom de projet de l'étape précédente) et, dans la boîte de dialogue, d'indiquer "Create from Existing Sources".
NB : il est en fait possible de créer un projet Vue CLI directement dans WebStorm (sans passer par le
vue create ...
dans la ligne de commande) en sélectionnant le type de projet "Vue.js"
directement dans le wizard. Mais bon... personnellement j'aime bien passer par la ligne de commande :).
Comme vous pouvez le remarquer, Vue CLI a généré toute une arborescence avec de nombreux fichiers. Nous n'allons pas tous les étudier, mais voici quelques informations sur les plus importants :
main.js
constitue le point d'entrée de l'application Vue.
Il "monte" votre application sur un composant racine
(id="app"
) défini dans index.html
.index.html
constitue le point d'entrée structurel.
Vous n'avez (a-priori) pas besoin d'y toucher, sauf pour changer le titre
et/ou le favicon.
App.vue
constitue le véritable point d'entrée pour
customiser le modèle généré par Vue CLI.
components/
va contenir les
composants .vue
qui vont être assemblés pour former l'application.
Le dossier assets
est destiné à recevoir les ressources de votre applications
(images, ...), node_modules
contient les librairies importées avec npm
,
et les autres fichiers servent principalement à la configuration et au déploiement de l'application.
App.vue
L'application Vue correspond au composant principal codé dans le fichier App.vue
.
Sa structure contient 3 parties :
template
: contient le code HTML qui décrit la structure de la l'application.
script
: contient le code Vue.js décrivant la partie fonctionnelle de l'application.
style
: contient les déclaration CSS de style pour l'application.
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/> <!-- insertion du composant de démo -->
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue' // importation du composant de démo
export default {
name: 'App',
components: {
HelloWorld // déclaration du composant de démo
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
On peut noter plusieurs points :
HelloWord
, qui a aussi été
généré par Vue CLI, et se trouve dans le fichiers HelloWorld.vue
.
components:{ ...}
)
permet de l'insérer en tant que nouvelle balise <HelloWorld>
dans le code
du template HTML de l'application.
En suivant cet exemple, il ne nous reste plus qu'à créer nos propres composants dans le dossier
./components
, à les importer et les déclarer dans l'application,
puis à les insérer dans le template.
On pourra aussi en même temps supprimer HelloWorld
qui ne servira plus à grand chose...
.vue
Pour créer un nouveau composant, il faut tout d'abord créer un fichier .vue
.
Dans un grand élan d'inspiration (et d'originalité) nous allons créer un composant nommé...
TestComposant
(!), ce qui revient à créer un fichier TestComposant.vue
dans le dossier /components
.
TestComposant.vue
<template>
<!-- ici démarre le contenu du composant -->
<div> <!-- NB : il faut un élément 'racine'-->
{{ message }}
</div>
</template>
<script>
export default {
name: "TestComposant", // le nom du composant
data(){return{
message: "Je suis un Composant de Test :)"
}}
}
</script>
<style scoped>
</style>
Dans WebStorm, il suffit de faire un click droit et "New -> Vue Component".
Il est possible de séparer les éléments du composant en plusieurs fichiers.
Le code du template peut être placé dans un fichier externe que l'on référencera grâce à
l'attribut src
de la balise template
au sein du fichier .vue
.
Dans le fichier .vue
<template src='fichier.html'></template>
Le code CSS peut être placé dans un fichier externe que l'on référencera grâce à
l'instruction @import
de la balise style
au sein du fichier .vue
.
Dans le fichier .vue
<style scoped>
@import "css/styles.css" ;
</style>
Une fois le composant créé, nous pouvons l'utiliser dans le fichier App.vue
.
Pour intégrer un composant il faut :
import
dans le script.
components
.
template
de l'application.
App.vue modifié :
<template>
<div id="app">
<test-composant></test-composant>
</div>
</template>
<script>
import TestComposant from "@/components/TestComposant";
export default {
name: 'App',
components: {
TestComposant
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
</style>
Pour vérifier que vous avez tout compris à la création et l'intégration de composants, transformez
l'exercice 01
en un composant nommé ex_01.vue
, et intégrez le dans l'application précédente, en dessous
de TestComposant.vue
, en séparant les 2 avec un <hr>
.
Les composants Vue sont faits pour être réutilisés et, de fait, ils peuvent être paramétrés.
La déclaration des noms des paramètres qui peuvent être reçus par un composant est faite dans le champ
props
. Leur utilisation est similaire aux data
.
smpl_01_fakecard.vue - 2 paramètres (name et mailServer)
<template>
<div>
<div class="mdl-card mdl-shadow--3dp"
style="background: linear-gradient(45deg, #ffffff, #607D8B) ; margin: 10px 0">
<div class="mdl-card__title mdl-card--expand">
<div v-show="name != ''">
<h4>
{{ name }}
</h4>
<h3>
<p>Mail : {{name}}@{{ mailServer }} </p>
</h3>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "smpl_01_fakecard",
props:{ // <------------ déclaration des paramètres ---------------------------
name: String, // le type n'est pas obligatoire, mais c'est mieux de le mettre
mailServer: String
}
}
</script>
<style scoped>
.mdl-card{
min-height: 70px ;
}
</style>
Une application (ou un autre composant) peut alors intégrer le composant en donnant des valeurs effectives à ses paramètres.
v-bind
(ou :
), pour des valeurs dynamiques
(Javascript).
name
reçoit une valeur dynamique, alors que
mailServer
reçoit une valeur statique.
App.vue
<template>
<div id="app">
<fakecard :name="userName" mailServer="truc.fr"></fakecard>
</div>
</template>
<script>
import smpl_01_fakecard from "@/components/smpl_01_fakecard";
export default {
name: "App",
components:{
fakecard: smpl_01_fakecard
},
data(){ return {
userName: 'Greg'
}},
}
</script>
Comme vous pouvez le voir dans la syntaxe ci-dessous, il est aussi possible de donner aux props
une valeur
par défaut (qui sera remplacée si une autre valeur est donnée lors de l'intégration du composant).
smpl_01_fakecard.vue - 2 paramètres (name et mailServer)
...
<script>
export default {
name: "smpl_01_fakecard",
props:{
name: {
type: String,
default: 'unknown'
},
mailServer: {
type: String,
default: 'bidule.fr'
}
},
}
</script>
En utilisant la classe User
qui vous est fournie dans le fichier
User.js, créez une application qui intègre
composant nommé ex_06_UserCard.vue
, sachant que ce composant est une encapsulation
de la "carte de visite" créée dans la partie droite du
TP01.
(cf. exemple ci-dessous).
Il y aura donc 2 fichiers : App.vue
et ex_06_UserCard.vue
.
Les exemples ci-avant montrent qu'il est possible de passer des objets en paramètre d'un composant. Il est cependant nécessaire de comprendre que ces objets sont liés selon le principe du one-way-down binding.
props
ne sont pas répercutées au niveau de l'objet du parent.
De ce fait, pour éviter toute ambigüité, si l'on veut que le composant fils manipule une donnée qui a été
initialisée par le composant parent, il est conseillé de crée un objet data
initialisé
avec l'objet props
.
L'exemple ci-dessous expérimente les liens et répercutions de modifications à tous les niveaux :
globalName
qui est envoyée en paramètre.
propName
dont la valeur est reçue en paramètre.
localName
initialisée grâce à propName
.
smpl_03_App_owb.vue
<template>
<div id="app">
(parent->data) globalName : <input class="input-sm" type="text" v-model="globalName"> -> {{ globalName }}
<hr>
<fakecard :propName="globalName"></fakecard>
</div>
</template>
<script>
import smpl_03_fakecard_owb from "@/components/samples/smpl_03_fakecard_owb";
export default {
name: "App",
components:{
fakecard: smpl_03_fakecard_owb
},
data(){ return {
globalName: 'Greg'
}},
}
</script>
smpl_03_fakecard_owb.vue
<template>
<div>
<label>(fils->props) propName</label> :
<input class="input-sm" type="text" v-model="propName">
{{ propName }}
<br>
<label>(fils->data) localName</label> :
<input class="input-sm" type="text" v-model="localName">
{{ localName }}
</div>
</template>
<script>
export default {
name: "smpl_03_fakecard_owb",
props:{ // <------------ déclaration des paramètres ---------------------------
propName: String, // le type n'est pas obligatoire, mais c'est mieux de le mettre
},
data(){return {
localName: this.propName
}}
}
</script>
<style scoped>
.mdl-card{
min-height: 70px ;
}
</style>
Complétez l'application créée dans l'exercice 06 en intégrant à l'application le formulaire qui a été créé lors du TP01 (partie gauche).
Il y aura donc 2 fichiers : App.vue
et ex_06_UserCard.vue
.
NB : le formulaire est pour l'instant directement intégré dans App.vue
Il est possible d'utiliser les composants comme des balises HTML avec une boucle v-for
,
le paramètre envoyé au composant étant la variable de boucle.
Créez un composant ex_08_Country.vue
qui sert à représenter 1 (seul) pays (tels qu'ils
sont construits dans countries), puis utilisez-le dans une
application qui affiche la liste des countries
sous cette forme.
Il peut parfois être nécessaire pour un composant fils de prévenir son parent d'un évènement particulier.
Vue.js permet aux composants fils de générer leurs propres évènements, et aux composants parents de les récupérer.
$emit('nom-evenement', valeur)
permet la levée d'un évènement dans un composant fils.
La récupération dans le parent est réalisée par un "classique" v-on
ou @
.
Exemple :
smpl_05_TextForm.vue
fournit un input de type texte et un bouton de
validation. Lorsque l'utilisateur clique sur le bouton "Valider", le composant émet un évènement
"text-validated" avec le contenu de l'input comme valeur.
smpl_05_App_emit.vue
utilise smpl_05_TextForm.vue
.
Il écoute ce composant sur l'évènement "text-validated". Lorsqu'il reçoit cet évènement, le texte
reçu du composant fils est affiché en tant que "Texte validé" dans le parent.
smpl_05_TextForm.vue
<template>
<div class="text-form" style="display: flex">
<input class="form-control" v-model="textInput" placeholder="Entrez votre texte">
<div style="width: 1em"></div>
<button @click="validate" class="btn btn-primary">
Valider
</button>
</div>
</template>
<script>
export default {
data(){ return {
textInput: ''
}},
methods:{
validate(){
this.$emit('update:text', this.textInput)
}
}
}
</script>
<style scoped>
.text-form{
width: 300px;
}
</style>
smpl_05_App_emit.vue
<template>
<div id="app">
<text-form @update:text="updateText"></text-form>
<hr>
Texte validé : {{ text }}
</div>
</template>
<script>
import smpl_05_TextForm from "@/components/samples/smpl_05_emit/smpl_05_TextForm";
export default {
name: "App",
components:{
TextForm: smpl_05_TextForm
},
data(){ return {
text: ''
}},
methods:{
updateText(txt){
this.text = txt
}
}
}
</script>
Vous pourrez noter qu'à la différence de ce que nous avons fait avec les props dans
l'exercice 07,
l'utilisation d'un évènement de composant permet de ne transmettre des informations d'un fils à un parent
que lorsque le fils l'a décidé.
Ce mécanisme est très pratique car il permet à un fils de recevoir des données d'un parent au travers
d'un paramètre props, de travailler sur une version locale data, et de proposer des
modifications à son parent au traver d'un $emit (au lieu de "bidouiller" une version partagée
avec les problèmes d'effets de bords que cela peut entraîner).
Quelques remarques à prendre en compte :
UserCard.vue
ET UserForm.vue
!UserForm.vue
.
ex_07_UserForm.vue
doit être un peu modifié pour pourvoir mettre en oeuvre
les mécanismes liés au bouton "Enregistrer" (activé/désactivé).tp_02_UserForm.vue
par ex.)
Dernière étape (pour l'instant ;) ), on va maintenant encapsuler ce qui a été fait en ce début de TP 02
dans un "gros" composant nommé UserInformation
et lui-même l'intégrer dans une "grosse"
application qui possèdera de plus une entête fournie par un nouveau composant nommé Header.vue
.
Quelques remarques à prendre en compte :
UserInformation
et Header
.
header
affiche le nick
de l'utilisateur. Cependant,
faites bien attention au fait que cette information n'est mise à jour que lorsqu'on l'a modifiée
ET enregistrée en cliquant sur le bouton éponyme dans UserInformation
!
UserInformation
continue d'utiliser UserCard
et UserForm
.
UserInformation
, le bouton "Effacer" a été remplacé par "Reset" : vous noterez
que son effet a changé aussi -> il réinitialise l'utilisateur avec les valeurs correspondant
au dernier enregistrement !
Du fait qu'il faut parfois travailler sur une copie locale de l'utilisateur pour ensuite la
valider lors d'un "Enregistrer", User.js
a été complété pour permettre le clonage (méthode clone()
).
Pour le composant Header
, j'ai utilisé une b-navbar
de
Boostrap Vue.
La syntaxe d'insertion des composants Vue permet d'utiliser les composants comme des balises HTML qui peuvent avoir du contenu.
La balise <slot>...</slot>
à l'intérieur du template
d'un
composant permet de d'injecter du code HTML déclaré à l'intérieur de la balise du composant au moment
de son insertion.
L'exemple ci-dessous montre comment un composant PostIt
peut être défini et utilisé en
usilisant un slot
destiné à recevoir le contenu du post-it.
PostIt.vue
<template>
<div>
<div class="mdl-card mdl-shadow--3dp post-it" :style="'background: '+ bg">
<slot>
<!-- on trouvera ici le contenu de la balise -->
</slot>
</div>
</div>
</template>
<script>
export default {
name: "PostIt",
props:{
bg: String
}
}
</script>
<style scoped>
.post-it{
width: 200px;
height: 200px;
padding: 15px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
smpl_06_App_slots.vue
<template>
<div id="app">
<post-it bg="lightpink">
TP FWF à rendre ! <!-- contenu inséré dans le template du composant -->
</post-it>
<post-it bg="yellow">
<!-- contenu inséré dans le template du composant -->
<h4>Courses :</h4>
<ul>
<li>poulet</li>
<li>maroilles</li>
<li>crème fraiche</li>
<li>frites au four</li>
</ul>
</post-it>
<post-it bg="lightblue">
Anniv Greg <!-- contenu inséré dans le template du composant -->
</post-it>
</div>
</template>
<script>
import PostIt from "@/components/samples/smpl_06_slots/PostIt";
export default {
name: "App",
components:{
PostIt
},
data(){ return {
}},
}
</script>
<style>
#app{
display: flex;
}
.post-it{
margin: 5px;
}
</style>
NB: il est aussi possible de créer des composants avec plusieurs slots
en leur donnant des noms,
de leur donner du contenu par défaut, et de leur passer des props
...
Toute la documentation se trouve
ici.
Les composants Vue ont un cycle de vie bien défini qu'il est intéressant de connaître, qui plus est du fait qu'il est possible d'associer des traitements à chaque étape.
Voici les différentes étapes telles que décrites dans la documentation officielle :
mounted
Dans l'exemple suivant, un nouvelle version de post-it (PostIt_V2
) a pour particularité
de calculer dynamiquement le nombre de tâches indiquées sur le post-it en se basant sur le nombre
de balises li
qui ont été injectées dans son slot
.
Ce nombre de li
ne pouvant être déterminé par le composant (lui-même) qu'une fois qu'il
a effectivement été créé, on utilise un handler sur l'evènement mounted
du composant.
Vous pourrez noter le recours à this.$el
pour référencer l'élément HTML correspondant au
composant Vue dans lequel le code se trouve.
PostIt_V2.vue
<template>
<div>
<div class="mdl-card mdl-shadow--3dp post-it" :style="'background: '+ bg">
<div class="mdl-card__title mdl-card--expand">
<slot></slot>
</div>
<div class="mdl-card__actions mdl-card--border"
style="background: white ; font-size: 0.8em ; text-align: center">
Il y a {{ nbTasks }} tache<span v-if="nbTasks>1">s</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: "PostIt",
props:{
bg: String
},
data(){return {
nbTasks: 0,
}},
mounted() { // déclenchement une fois le composant "monté"
let monElement = this.$el // permet de récupérer l'élément HTML du composant
// calcul dynamique du nombres de tâches
this.nbTasks = monElement.getElementsByTagName('li').length
}
}
</script>
<style scoped>
.post-it{
width: 200px;
height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
smpl_07_App_lifecycle.vue
<template>
<div id="app">
<post-it-v2 bg="yellow">
<ul>
<li>truc à faire</li>
<li>truc à faire</li>
<li>truc à faire</li>
</ul>
</post-it-v2>
<post-it-v2 bg="lightpink">
<ul>
<li>quelque chose</li>
<li>quelque chose</li>
</ul>
</post-it-v2>
<post-it-v2 bg="lightblue">
<ul>
<li>juste ça</li>
</ul>
</post-it-v2>
</div>
</template>
<script>
import PostIt_V2 from "@/components/samples/smpl_07_lifecycle/PostIt_V2";
export default {
name: "App",
components:{
postItV2 : PostIt_V2
},
data(){ return {
}},
}
</script>
<style>
#app{
display: flex;
}
.post-it{
margin: 5px;
}
</style>
Le but de ce TP est de créer une page qui utilise 2 composants spécifiques :
Spoiler
qui, comme son nom l'indique, permet d'intégrer dans la page
des composants qui affichent un bouton spoiler
à la place d'un texte spécifique, ce
texte n'étant visible qu'une fois qu'on a cliqué sur le bouton correspondant.
NavItem
qui génère une ancre autour d'un texte : le texte affiché
est le contenu de la balise du composant (lors de son insertion) et le lien correspond au paramètre
du composant nommé href
.
Pour réaliser cette application, vous sont fournis :
template.html
qui contient le template de l'application :Ces fichiers sont contenus dans l'archive tp03_ressources.zip.
Pour l'intégration de template.html
et la création d'un fichier CSS externe,
vous pouvez vous reporter aux explications données
dans le cours.
Quelques remarques à prendre en compte :
template.html
NE DOIT PAS ÊTRE MODIFIÉ ! (oui... j'insiste...)spoiler
sont déjà présentes dans le template.
nav-item
composant la navbar
est générée dynamiquement à partir d'une variable titles
. id
et h1
contenus dans les articles
du template de l'application.NB: Ce TP est une version Vue du TP Javascript donné ici.