Les bases de Vue
Pour bien commencer avec vue 3
Vue est un framework Javascript destiné à la création d’interface utilisateur. Vue est conçu pour être “progressif”. C’est à dire qu’il conviendra à plusieurs conceptions ou besoins différents comme vous pourrez le lire dans la documentation
Commencer avec Vue
Le CDN
En allant sur la doc de Vue vous trouverez le CDN permettant d’intégrer directement vue à une page html.
Il suffit d’ajouter ce script à la page.
Par exemple:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Vue avec le CDN</title>
<!-- Import Styles -->
<link rel="stylesheet" href="./assets/styles.css" />
<!-- Import Vue.js -->
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<h1>Les produits seront ici</h1>
</div>
<!-- Import Js -->
<script src="./main.js"></script>
</body>
</html>
vous pouvez déjà cloner ce dépot pour partir de ce niveau.
git clone https://github.com/germainsip/vuecdn.git && cd vuecdn
puis git checkout 1-intro-debut
Nous avons déjà un style.css un main.js et des images dans notre projet:
vuecdn
├── assets
│ ├── images
│ │ ├── socks_blue.jpg
│ │ └── socks_green.jpg
│ └── styles.css
├── index.html
└── main.js
Ouvrons le main.js
et modifions le pour créer une application vue:
const app = Vue.createApp({
data() {
return {
product: 'Chaussettes'
}
}
})
Nous avons ici à l’aide de la méthode de vue createApp()
créé une application vue. Cette méthode prend en paramètre une fonction data()
qui renvoie un objet {product: 'Chaussettes'}
.
Pour le moment, notre index.html ne connait pas l’appli. Il faut a monter à l’emplacement de notre choix:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Vue avec le CDN</title>
<!-- Import Styles -->
<link rel="stylesheet" href="./assets/styles.css" />
<!-- Import Vue.js -->
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<h1>Les {{product}} seront ici</h1>
</div>
<!-- Import App -->
<script src="./main.js"></script>
<!--Mount App-->
<script>
const mountedApp = app.mount('#app')
</script>
</body>
</html>
A ce stade, si vous ouvrez le fichier index.html
(via live server) vous devriez avoir:
Vous voyez que les “moustaches” {{product}}
ont été remplacées par la valeur contenue dans l’objet. Vous pouvez également mettre des expressions javascript dans ces “moustaches”.
Par exemple, {{product.toUpperCase()}}
mettra Chaussettes en majuscule.
En guise d’application, ajoutez sous titre (<h2>
) avec une description dans l’application vue.
Vous aurez la solution en accédant à la branche 1-intro-fin
.
Le binding
Pour ceux qui voudraient commencer à partir du même point. Passez sur la branche 2-binding-debut
.
J’ai ajouté une balise dans notre html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Vue avec le CDN</title>
<!-- Import Styles -->
<link rel="stylesheet" href="./assets/styles.css" />
<!-- Import Vue.js -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<div class="nav-bar"></div>
<div class="product-display">
<div class="product-container">
<div class="product-image">
<!-- image goes here -->
</div>
<div class="product-info">
<h1>{{ product }}</h1>
</div>
</div>
</div>
</div>
<!-- Import App -->
<script src="./main.js"></script>
<!--Mount App-->
<script>
const mountedApp = app.mount('#app')
</script>
</body>
</html>
Nous avons déjà des images dans le dossier assets
ajoutons cet attribut dans notre app
const app = Vue.createApp({
data() {
return {
product: 'Chaussettes',
image: './assets/images/socks_blue.jpg'
}
}
})
Afin d’ajouter cette valeur à une balise <img>
nous allons utiliser l’attribut v-bind:
dans index.html
:
<!--...-->
<div id="app">
<div class="nav-bar"></div>
<div class="product-display">
<div class="product-container">
<div class="product-image">
<img v-bind:src="image" alt="chaussettes">
</div>
<div class="product-info">
<h1>{{ product }}</h1>
</div>
</div>
</div>
</div>
<!--...-->
Ce qui nous donne ça:
Nous avons ici connecté l’attribut src
de <img>
à la propriété image de notre application.
On peut raccourcir l’écriture en écrivant uniquement :src
.
En guise d’exercice, ajoutez une propriété url dans l’application et bindez la dans un lien (<a href>
).
solution à la branche 2-binding-fin
.
Le rendu conditionnel
Avec Vue, nous pouvons faire du “rendue conditionnel”. C’est à dire afficher quelque chose en fonction d’une condition.
Par exemple, nous pourrions afficher si la chaussette choisie est en stock ou non.
Ajoutons les deux propositions dans l’html:
<div class="product-display">
<div class="product-container">
<div class="product-image">
<img v-bind:src="image" alt="chaussettes">
</div>
<div class="product-info">
<h1>{{ product }}</h1>
<p>En stock</p>
<p>Rupture</p>
</div>
</div>
</div>
Côté javascript, ajoutons un booléen:
const app = Vue.createApp({
data() {
return {
product: 'Chaussettes',
image: './assets/images/socks_blue.jpg',
inStock: true
}
}
})
Pour Vue, nous avons une directive qui permet de faire ce que nous avons envie de faire. Il s’agit de v-if
et v-else
. Modifions donc index.html
.
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
</div>
Dès l’ors si vous changez la valeur de inStock
l’affichage sera mis à jour avec respectivement “En stock” ou “Rupture”.
Rq:
v-else
n’est pas obligatoire si vous n’avez pas de valeur alternative à afficher.
Dans le cas où il ne s’agit que de faire apparaitre ou disparaitre un élément nous pouvons utiliser v-show
qui agira sur la visibilité d’un élément (style= "display: none"
).
Nous pouvons faire plus élaboré. Notre texte pourrait changer selon certaines valeurs.
Changeons la propriété dans mains.js
const app = Vue.createApp({
data() {
return {
product: 'Chaussettes',
image: './assets/images/socks_blue.jpg',
inventory : 100
}
}
})
Et dans index.html
nous pouvons ajouter une étape:
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inventory > 10">En stock</p>
<p v-else-if="inventory <= 10 && inventory > 0">Petites quantités</p>
<p v-else>Rupture</p>
</div>
Afficher une liste d’éléments
Nous en sommes à la branche
4-list-debut
Dans main.js
nous avons la propriété details: ['50% coton', '30% laine', '20% polyester']
qui est une liste de valeurs.
Afin de les afficher, nous allons utiliser une autre directive v-for
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
<p>Détail:</p>
<ul>
<li v-for="detail in details">{{detail}}</li>
</ul>
</div>
Nous pouvons également utiliser une liste d’objets:
//main.js
const app = Vue.createApp({
data() {
return {
product: 'Chaussette',
image: './assets/images/socks_blue.jpg',
inStock: true,
details: ['50% coton', '30% laine', '20% polyester'],
variants: [
{id: 1234 , color: 'bleu'},
{id: 3245 , color: 'vert'}
]
}
}
})
<!--index.html-->
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
<p>Détail:</p>
<ul>
<li v-for="detail in details">{{detail}}</li>
</ul>
<p>Existe en:</p>
<div v-for="variant in variants" :key="variant.id">{{variant.color}}</div>
</div>
Rq:
:key="variant.id"
est fortement conseillé car il donne une clé unique à chaque élément du DOM.
Les évènements
Partons de la branche 5-action-debut
ici
Comme vous pouvez le constater, j’ai ajouté un bouton et un compteur.
Afin d’écouter l’évènement click
et d’ajouter une paire de chaussette au panier nous allons utiliser la directive v-on
et lui affecter une action.
<div id="app">
<div class="nav-bar"></div>
<div class="cart">Panier({{ cart }})</div>
<div class="product-display">
<div class="product-container">
<div class="product-image">
<img v-bind:src="image" alt="chaussettes">
</div>
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
<p>Détail:</p>
<ul>
<li v-for="detail in details">{{detail}}</li>
</ul>
<p>Existe en:</p>
<div v-for="variant in variants" :key="variant.id">{{variant.color}}</div>
<!-- ICI -->
<button v-on:click="cart += 1" class="button">Ajouter au panier</button>
</div>
</div>
</div>
</div>
Maintenant, à chaque clique, la propriété cart
et augmenté de 1. Vue ne va alors rafraichir que la balise <div class="cart">Panier({{ cart }})</div>
.
Ca fonctionne mais ce serait mieux d’avoir un fonction (méthode) pour ça.
Ajoutons la méthode addToCart()
dans main.js
:
const app = Vue.createApp({
data() {
return {
cart:0,
product: 'Chaussette',
image: './assets/images/socks_blue.jpg',
inStock: true,
details: ['50% coton', '30% laine', '20% polyester'],
variants: [
{id: 1234 , color: 'bleu'},
{id: 3245 , color: 'vert'}
]
}
},
methods: {
addToCart() {
this.cart += 1
}
}
})
et remplaçons dans index.html
<button v-on:click="addToCart" class="button">Ajouter au panier<button>
De même que pour le v-bind
nous avons un raccourcit. Ici v-on:click
peut être remplacé par @click
.
Nous avons vu un évènement, mais il y en a d’autres. Par exemple, on hover
quand on survole un éléments. Mettons en place le changement d’image au survole de la couleur.
Commençons par ajouter les urls dans la liste variants
:
variants: [
{id: 1234 , color: 'bleu', image: './assets/images/socks_blue.jpg'},
{id: 3245 , color: 'vert', image: './assets/images/socks_green.jpg'}
]
Ajoutons également la méthode changeImg()
:
changeImg(variantImage) {
this.image = variantImage
}
et enfin ajoutons l’évènement à nos balises:
<div v-for="variant in variants" :key="variant.id" @mouseOver="changeImg(variant.image)">{{variant.color}}</div>
Et voilà:
Pour améliorer notre panier, vous pouvez ajouter un bouton pour enlever un élément du panier. (solution à 5-action-fin
)
Le binding de classe et de style
Plutôt que d’avoir bleu et vert, nous allons mettre des pastilles de couleurs. Commençons par ajouter une classe à notre balise:
<div class="color-circle" v-for="variant in variants" :key="variant.id" @mouseOver="changeImg(variant.image)">{{variant.color}}</div>
cette classe est déjà écrite dans le style.css
:
.color-circle {
width: 50px;
height: 50px;
margin-top: 8px;
border: 2px solid #d8d8d8;
border-radius: 50%;
}
Remplaçons, maintenant, le texte par un background grâce à la directive :style
.
<div
class="color-circle"
:style="{backgroundColor: variant.back}"
v-for="variant in variants" :key="variant.id"
@mouseOver="changeImg(variant.image)"></div>
Et dans main.js
ajoutons nos valeurs dans variants
variants: [
{id: 1234 , color: 'bleu', back: '#38475F', image: './assets/images/socks_blue.jpg'},
{id: 3245 , color: 'vert', back: '#4F9869', image: './assets/images/socks_green.jpg'}
]
Dorénavant nos couleurs apparaissent sous forme de pastilles.
Nous pouvons aussi agir sur d’autres attributs. Par exemple, désactivons les boutons si le stock est en rupture. Pour nos deux boutons cela donnerait:
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="addToCart" >
Ajouter au panier
</button>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="remFromCart" >
Enlever du panier
</button>
Ici nous faisons deux choses. D’une part, si inStock=false
les boutons passent à disabled
et la classe passe à disabledButton
décrite dans le css par:
.disabledButton {
background-color: #d8d8d8;
cursor: not-allowed;
}
Propriétés calculées
Nous pouvons avoir des propriétés calculées (compilé). C’est à dire, qu’elles nécessites un traitement. Par exemple, ajoutons des quantités dans notre liste et gérons le stock ainsi que l’image par calcul.
Modifions déjà l’html:
<div id="app">
<div class="nav-bar"></div>
<div class="cart">Panier({{ cart }})</div>
<div class="product-display">
<div class="product-container">
<div class="product-image">
<img v-bind:src="image" alt="chaussettes" />
</div>
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
<p>Détail:</p>
<ul>
<li v-for="detail in details">{{detail}}</li>
</ul>
<p>Existe en:</p>
<div
v-for="(variant, index) in variants"
:key="variant.id"
@mouseover="updateVariant(index)"
class="color-circle"
:style="{ backgroundColor: variant.back }">
</div>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="addToCart"
>
Ajouter au panier
</button>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="remFromCart"
>
Enlever du panier
</button>
</div>
</div>
</div>
</div>
La méthode changeImg
est remplacée par updateVariant
et le v-for
prend (variant,index)
afin d’avoir l’index de l’élément dans le tableau.
Côté javascript, on va faire plus de modifications. D’abords, ajoutons la méthode updateVariant
et modifions les propriétés pour avoir selectedVariant
mais pas image
et inStock
qui vont être calculées.
const app = Vue.createApp({
data() {
return {
cart:0,
product: 'Chaussette',
selectedVariant: 0,
details: ['50% coton', '30% laine', '20% polyester'],
variants: [
{id: 1234 , color: 'bleu', back: '#38475F', image: './assets/images/socks_blue.jpg',quantity: 50},
{id: 3245 , color: 'vert', back: '#4F9869', image: './assets/images/socks_green.jpg',quantity:0}
]
}
},
methods: {
addToCart() {
this.cart += 1
},
remFromCart() {
if(this.cart > 0) {
this.cart -= 1
}
},
updateVariant(index) {
this.selectedVariant = index
}
}
})
Retrouvons nos propriétés dans computed
const app = Vue.createApp({
data() {
return {
cart:0,
product: 'Chaussette',
selectedVariant: 0,
details: ['50% coton', '30% laine', '20% polyester'],
variants: [
{id: 1234 , color: 'bleu', back: '#38475F', image: './assets/images/socks_blue.jpg',quantity: 50},
{id: 3245 , color: 'vert', back: '#4F9869', image: './assets/images/socks_green.jpg',quantity:0}
]
}
},
methods: {
addToCart() {
this.cart += 1
},
remFromCart() {
if(this.cart > 0) {
this.cart -= 1
}
},
updateVariant(index) {
this.selectedVariant = index
}
},
computed: {
image() {
return this.variants[this.selectedVariant].image
},
inStock() {
return this.variants[this.selectedVariant].quantity
}
}
})
Cette fois, quand on survole les pastilles, l’image, le stock et les boutons sont mis à jour.
Les composants et les props
Les frameworks comme vue, react, angular… sont conçu pour fonctionner par composants. L’idée est de séparer la page en éléments indépendants et réutilisables.
Nous allons découper notre application en composants.
Tout d’abord, créez un dossier components
et ajoutez y un fichier ProductDisplay.js
Dans ce fichier nous allons créer la partie qui affiche le produit. Déplaçons toute la partie display:
// ProductDisplay.js
app.component('product-display', {
template:
/*html*/
`<div class="product-display">
<div class="product-container">
<div class="product-image">
<img v-bind:src="image" alt="chaussettes" />
</div>
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
<p>Détail:</p>
<ul>
<li v-for="detail in details">{{detail}}</li>
</ul>
<p>Existe en:</p>
<div
v-for="(variant, index) in variants"
:key="variant.id"
@mouseover="updateVariant(index)"
class="color-circle"
:style="{ backgroundColor: variant.back }">
</div>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="addToCart"
>
Ajouter au panier
</button>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="remFromCart"
>
Enlever du panier
</button>
</div>
</div>
</div>`,
data() {
return {
product: 'Chaussette',
selectedVariant: 0,
details: ['50% coton', '30% laine', '20% polyester'],
variants: [
{id: 1234 , color: 'bleu', back: '#38475F', image: './assets/images/socks_blue.jpg',quantity: 50},
{id: 3245 , color: 'vert', back: '#4F9869', image: './assets/images/socks_green.jpg',quantity:0}
]
}
},
methods: {
addToCart() {
this.cart += 1
},
remFromCart() {
if(this.cart > 0) {
this.cart -= 1
}
},
updateVariant(index) {
this.selectedVariant = index
}
},
computed: {
image() {
return this.variants[this.selectedVariant].image
},
inStock() {
return this.variants[this.selectedVariant].quantity
}
}
})
Dans index.html
nous pouvons maintenant importer notre composant et utiliser la balise qui porte sont nom.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Vue avec le CDN</title>
<!-- Import Styles -->
<link rel="stylesheet" href="./assets/styles.css" />
<!-- Import Vue.js -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<div class="nav-bar"></div>
<div class="cart">Panier({{ cart }})</div>
<product-display></product-display>
</div>
<!-- Import App -->
<script src="./main.js"></script>
<!-- Import ProductDisplay -->
<script src="./components/ProductDisplay.js"></script>
<!--Mount App-->
<script>
const mountedApp = app.mount("#app");
</script>
</body>
</html>
Nous pourrions ajouter autant de versions indépendantes de ce composant.
Les props
Notre composant doit pouvoir communiquer avec le reste de l’application. Les props
sont là pour ça.
Pour illustrer cette prop
ajoutons une propriété premium
au main.js
. Cette propriété dit si l’usager a un compte premium ou non. Dans le composant nous afficherons les frais de port (gratuit pour premium, 5 € pour les autres).
Dans main.js
ça donne:
const app = Vue.createApp({
data() {
return {
cart:0,
premium: true
}
},
methods: {}
})
Et dans le composant ProductDisplay.js
:
- On ajoute la prop en entré en spécifiant son type.
- On ajoute une balise
<p>
avec les frais de ports qui dépendent de la méthodeshipping
. - Que nous ajoutons également.
app.component("product-display", {
props: {
premium: {
type: Boolean,
required: true,
},
},
template:
/*html*/
`<div class="product-display">
<div class="product-container">
<div class="product-image">
<img v-bind:src="image" alt="chaussettes" />
</div>
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
<p>Frais de port: {{shipping}}</p>
<p>Détail:</p>
<ul>
<li v-for="detail in details">{{detail}}</li>
</ul>
<p>Existe en:</p>
<div
v-for="(variant, index) in variants"
:key="variant.id"
@mouseover="updateVariant(index)"
class="color-circle"
:style="{ backgroundColor: variant.back }">
</div>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="addToCart"
>
Ajouter au panier
</button>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="remFromCart"
>
Enlever du panier
</button>
</div>
</div>
</div>`,
data() {
return {
product: "Chaussette",
selectedVariant: 0,
details: ["50% coton", "30% laine", "20% polyester"],
variants: [
{
id: 1234,
color: "bleu",
back: "#38475F",
image: "./assets/images/socks_blue.jpg",
quantity: 50,
},
{
id: 3245,
color: "vert",
back: "#4F9869",
image: "./assets/images/socks_green.jpg",
quantity: 0,
},
],
};
},
methods: {
addToCart() {
this.cart += 1;
},
remFromCart() {
if (this.cart > 0) {
this.cart -= 1;
}
},
updateVariant(index) {
this.selectedVariant = index;
},
},
computed: {
image() {
return this.variants[this.selectedVariant].image;
},
inStock() {
return this.variants[this.selectedVariant].quantity;
},
shipping() {
if (this.premium) {
return "Gratuit";
}
return "5 €";
},
},
});
Communiquer en dehors du composant.
Nous avons factorisé notre code en composant, mais les boutons ne fonctionnent plus. Comment réparer ça?
Nous devons faire sortir l’évènement du composant avec un $emit
//ProductDisplay.js
...
methods: {
addToCart() {
this.$emit("add-to-cart")
},
remFromCart() {
this.$emit("rem-from-cart")
},
updateVariant(index) {
this.selectedVariant = index;
},
},
...
Quand les boutons seront cliqués, ils enverront un évènement (resp add-to-cart
et rem-from-cart
). Il faut les écouter dans index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Vue avec le CDN</title>
<!-- Import Styles -->
<link rel="stylesheet" href="./assets/styles.css" />
<!-- Import Vue.js -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<div class="nav-bar"></div>
<div class="cart">Panier({{ cart }})</div>
<product-display :premium="premium" @add-to-cart="addToCart" @rem-from-cart="remFromCart"></product-display>
</div>
<!-- Import App -->
<script src="./main.js"></script>
<!-- Import Import ProductDisplay -->
<script src="./components/ProductDisplay.js"></script>
<!--Mount App-->
<script>
const mountedApp = app.mount("#app");
</script>
</body>
</html>
Et mettons à jour les méthodes dans main.js
const app = Vue.createApp({
data() {
return {
cart: 0,
premium: true
}
},
methods: {
addToCart(){
this.cart++
},
remFromCart(){
if(this.cart>0){
this.cart--
}
}
}
})
Petit clean-up
En réalité, ce serait beaucoup mieux pour le panier d’avoir un tableau qui liste les éléments achetés.
//main.js
const app = Vue.createApp({
data() {
return {
cart: [],
premium: true
}
},
methods: {
addToCart(id){
this.cart.push(id)
},
remFromCart(id){
// TODO
}
}
})
Modifions également ProductDisplay.js
...
methods: {
addToCart() {
this.$emit("add-to-cart",this.variants[this.selectedVariant].id)
},
remFromCart() {
this.$emit("rem-from-cart")
},
updateVariant(index) {
this.selectedVariant = index;
},
},
...
Pensez à passer la quantité de chaussettes vertes à 20 et testez.
Le panier devrait afficher la liste des id
.
Corrigez en modifiant <div class="cart">Panier({{ cart }})</div>
en <div class="cart">Panier({{ cart.length }})</div>
Exercice: complétez remFromCart(id)
dans main.js
pour que le bouton “Enlever du panier” refonctionne. Et faites en sorte que le stock soit actualisé.
Correction: branche 9-emit-fin
Les formulaires
Depuis le début, nous avons parlé de “binder” (lier) dans le sens donné vers la vue.
Pour un formulaire, nous allons lier “binder” dans les deux sens.
Et pour ça nous utiliserons v-model
.
Créons un nouveau composant components/ReviewForm.js
:
app.component("review-form", {
template:
/*html*/
`<form class="review-form">
<h3>Laissez un avis</h3>
<label for="name">Nom:</label>
<input id="name" type="text" />
<label for="review">Avis:</label>
<textarea id="review"></textarea>
<label for="rating">Note:</label>
<select id="rating">
<option >5</option>
<option >4</option>
<option >3</option>
<option >2</option>
<option >1</option>
</select>
<input type="submit" class="button" value="Envoyer"/>
</form>`,
data() {
return {
name: "",
review: "",
rating: null,
};
},
});
Un simple formulaire avec 3 valeurs. Nous les retrouvons dans les data
.
Nous devons les relier ensembles avec v-model
:
<form class="review-form">
<h3>Laissez un avis</h3>
<label for="name">Nom:</label>
<input id="name" type="text" v-model="name" />
<label for="review">Avis:</label>
<textarea id="review" v-model="review"></textarea>
<label for="rating">Note:</label>
<select id="rating" v-model.number="rating">
<option >5</option>
<option >4</option>
<option >3</option>
<option >2</option>
<option >1</option>
</select>
<input type="submit" class="button" value="Envoyer"/>
</form>
Pour envoyer nos données, nous allons créer une méthode:
methods: {
onSubmit() {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.$emit('review-submitted', productReview);
this.name=''
this.review=''
this.rating=null
}
}
et ajouter un écouteur à <form>
<form class="review-form" @submit-prevent="onSubmit">
Côté ProductDisplay.js
ajoutons simplement la balise <review-form>
:
...
template:
/*html*/
`<div class="product-display">
<div class="product-container">
<div class="product-image">
<img v-bind:src="image" alt="chaussettes" />
</div>
<div class="product-info">
<h1>{{ product }}</h1>
<p v-if="inStock">En stock</p>
<p v-else>Rupture</p>
<p>Frais de port: {{shipping}}</p>
<p>Détail:</p>
<ul>
<li v-for="detail in details">{{detail}}</li>
</ul>
<p>Existe en:</p>
<div
v-for="(variant, index) in variants"
:key="variant.id"
@mouseover="updateVariant(index)"
class="color-circle"
:style="{ backgroundColor: variant.back }">
</div>
<button
class="button"
:class="{disabledButton: !inStock}"
:disabled="!inStock"
@click="addToCart"
>
Ajouter au panier
</button>
<button
class="button"
@click="remFromCart"
>
Enlever du panier
</button>
</div>
<!--ICI-->
<review-form></review-form>
</div>
</div>`,
...
Il va falloir récupérer l’évènement et l’ajouter à notre vue.
l’écouteur:
<review-form @review-submitted="addReview"></review-form>
Ajoutons la méthode et le tableau qui va receptionner les avis:
//ProductDisplay.js
...
data() {
return {
product: "Chaussette",
selectedVariant: 0,
details: ["50% coton", "30% laine", "20% polyester"],
variants: [
{
id: 1234,
color: "bleu",
back: "#38475F",
image: "./assets/images/socks_blue.jpg",
quantity: 50,
},
{
id: 3245,
color: "vert",
back: "#4F9869",
image: "./assets/images/socks_green.jpg",
quantity: 20,
},
],
reviews: [],
};
},
methods: {
addToCart() {
this.variants[this.selectedVariant].quantity--
this.$emit("add-to-cart",this.variants[this.selectedVariant].id)
},
remFromCart() {
this.variants[this.selectedVariant].quantity++
this.$emit("rem-from-cart",this.variants[this.selectedVariant].id)
},
updateVariant(index) {
this.selectedVariant = index;
},
addReview(review) {
this.reviews.push(review)
}
},
...
Pour afficher les avis nous allons créer un composant ReviewList.js
app.component('review-list',
{
props: {
reviews: {
type: Array,
required: true
}
},
template:
/*html*/
`<div class="review-container">
<h3>Avis:</h3>
<ul>
<li v-for="(review, index) in reviews" :key="index">
{{review.name}} a donné {{ '★'.repeat(review.rating) }}
<br />
"{{review.review}}"
</li>
</ul>
</div>`
})
Enfin ajoutons cette liste au dessus du formulaire dans ProductDisplay.js
et sans oublier d’importer notre composant dans index.html
<review-list :reviews="reviews"></review-list>
Améliorations
- Ajoutez une condition dans la balise
<review-list>
pour que les avis ne s’affichent que lorsqu’il y en a. - Ajoutez des conditions dans la méthode
onSubmit
pour vérifier que le tous les champs ont bien été remplis.