Vous êtes sur la page 1sur 21

Atelier Mise en place d’une application CRUD simple

avec Vue JS 3

Dans cet atelier, on va effectuer des opérations CRUD à l'aide de l'API Web dans VueJS. Les services
qu’on va consommer provenant d’un backend NodeJs. En effet, on va apprendre comment créer et
consommer un RESTful. Pour gérer les données dans un serveur, on utilisera GET, POST, PUT et Delete
à travers HttpClient API. De plus, on va intégrer le Framework Bootstrap aux interfaces créées.

Vue.js est un Framework progressif pour JavaScript utilisé pour créer des interfaces Web et des
applications d'une page (SPA). Vue.js peut également être utilisé pour le développement d'applications
de bureau et mobiles avec les frameworks Ionic et Electron.

1. Créer un nouveau projet VueJS


a. Pour installer le projet Vue, nous devons avoir Vue CLI installé sur notre système de
développement si ça n’a pas été fait au préalable. Exécutez la commande suivante pour
installer Vue CLI :
npm install -g @vue/cli

b. Utilisez la commande suivante pour installer le projet vue.


vue create monprojet

Nous devons choisir la version 3 de Vue.

c. Rendez-vous sur le projet de vue :


cd monprojet

d. Ouvrez le projet vue dans l’éditeur Visual Studio Code :


code .

e. Exécutez la commande pour démarrer l'application sur le navigateur :


npm run serve

f. Une fois le serveur hébergé et l'application démarrée, vous pouvez accéder à


http://localhost:8080/.

2. Installer les dépendances


a. Bootstrap

Bootstrap est une collection d'outils utiles à la création du design de sites et d'applications web.
npm install bootstrap
b. Axios

Pour les projets qui doivent s'interfacer avec une API REST, Axios est un client HTTP léger basé sur le
service $http et similaire à l'API Fetch.

Axios est basé sur des promesses et nous pouvons donc profiter de async et await plus lisible. Nous
pouvons également intercepter et annuler des demandes, et il existe une protection côté client
intégrée contre la falsification des demandes intersites.
npm install axios

c. Router

Le terme « Applications monopages » (SPA) indique qu'une seule page est chargée. Cette dernière est
ensuite mise à jour dynamiquement en fonction des besoins. Bien que cela soit puissant, il y a tout de
même un défaut principal : les utilisateurs ont besoin d'URL différentes pour distinguer les différentes
pages les unes des autres. Sinon, à chaque fois qu'un utilisateur essaie de revenir en arrière, il
obtiendra toujours la même page !

Pour résoudre ce problème, tous les grands Frameworks disposent d'une solution de routage. La
solution recommandée par Vue est la bibliothèque officielle vue-router.

Pour ajouter Vue Router à notre application, nous utiliserons Vue CLI :
vue add router

Lorsqu'on demande de choisir le mode historique, choisissez oui.

Une fois l'installation terminée, vous devriez observer que la CLI a modifié un certain nombre de
fichiers afin de s'assurer que tout fonctionne correctement. Si vous ouvrez votre fichier main.js, vous
verrez que Vue CLI a automatiquement configuré la propriété Router en la rattachant à votre instance
de Vue. De plus, un nouveau fichier est apparu, essentiel à la compréhension du routage :
router/index.js.

3. Démarrer le backend NodeJS


a. Dans le répertoire de l’applcation NodeJs exécuter node app

node app

b. La structure du model de « articles » dans NodeJs est la suivante :

import mongoose from "mongoose"


const articleSchema=mongoose.Schema({
reference:{ type: String, required: true,unique:true },
designation:{ type: String, required: true,unique:true },
prixAchat:{ type: Number, required: false },
prixVente:{ type: Number, required: false },
prixSolde:{ type: Number, required: false },
marque:{ type: String, required: true },
qtestock:{ type: Number, required: false },
caracteristiques:{ type: String, required: false },
imageartpetitf:{ type: String, required: false },
imageartgrandf:{ type: Array, required: false },
categorieID: {type:mongoose.Schema.Types.ObjectId,
ref:'Categorie'},
scategorieID: {type:mongoose.Schema.Types.ObjectId,
ref:'Scategorie'}
})
const Article = mongoose.model('Article', articleSchema);

export default Article

c. Les routes vers cette api sont les suivantes :

 Articles : http://localhost:3001/api/articles
 Catégories : http://localhost:3001/api/categories
 Sous Catégories : http://localhost:3001/api/scategories
 Sous Catégories pour une catégorie bien déterminée :
http://localhost:3001/api/scategories/cat/

4. Dans le dossier « src/assets » du projet Vue, créer un répertoire intitulé « images ». Puis y
télécharger les différentes images de produits qu’on va afficher dans notre site.

5. Mettre en place des imports dans main.js

Vue CLI crée plusieurs dossiers et fichiers. Le point de départ est public/index.html et "src/main.js".
Mais le component qui sert de point d'entrée est donc App.vue

Avec une application Vue, aucun code HTML ne sera écrit dans le fichier index.html. Votre code HTML
sera écrit dans la section de chacun de vos components

Le fichier main.js sert à configurer notre projet. Il précise les composants à utiliser (import App from
'./App.vue'), initialiser des variables globales (Vue.config.productionTip = false) et précise où le rendu
doit être effectué ($mount('#app'))).

Ce fichier est en quelque sorte le point central de l'application.

import { createApp } from 'vue'


import App from './App.vue'
import router from './router'
import 'bootstrap/dist/css/bootstrap.min.css'

createApp(App).use(router).mount('#app')

6. Créer les composants

Rendez-vous dans le répertoire src/components, ici nous devons créer les composants suivants. Ces
composants géreront les données dans notre application Vue.Js.

 AjoutProduit.vue
 EditProduit.vue
 Listproduits.vue

Le squelette de code à mettre dans chacun des composants est la suivante :


<template>
<div class="row justify-content-center">
<div class="col-md-6">
<!-- Contenu -->
</div>
</div>
</template>

<script>
export default {
data() {
return {
}
}
}
</script>
</div>

7. Mise en place des routes

Il existe deux composants principaux que Vue Router utilise et que vous devrez apprivoiser pour vos
applications :

<router-view></router-view> - il définit la zone de la page dans laquelle apparaîtra le composant que


nous définissons dans chaque route. Ce component est fourni avec la librairie vue-router et agit
comme conteneur pour rendre les routes que nous avons défini.

<router-link></router-link> - similaire à la balise anchor en HTML, ce composant permet également


la navigation de l'utilisateur dans l'application. Cependant, il est privilégié par rapport à l'utilisation
d'une balise d'ancrage standard, car il dispose de fonctionnalités intégrées, comme le fait qu'il évite
de recharger systématiquement la page.

Ces deux composants constituent la base de l'utilisation de Vue Router dans nos composants. Ils
facilitent la navigation dans une application Vue.

Ouvrez le fichier router/index.js puis modifiez son contenu ainsi :

import { createRouter, createWebHistory } from 'vue-router'


import ListComponent from '../components/Listproduits'
import CreateComponent from '../components/AjoutProduit'
import EditComponent from '../components/EditProduit'
const routes = [
{
path: '/',
name: 'Home',
component: ListComponent
},
{
path: '/add',
name: 'Ajout',
component: CreateComponent
},
{
path: '/edit/:id',
name: 'Edit',
component: EditComponent
}
]

const router = createRouter({


history: createWebHistory(process.env.BASE_URL),
routes
})

export default router

L'option d'historique lors de la création de l'instance de routeur nous permet de choisir parmi
différents modes d'historique.

Le mode d'historique de hachage est créé avec createWebHistory.

Vue dispose d'un système de fichiers en cascade qui déterminera les variables d'environnement pour
votre application. Le fichier principal étant un .env

BASE_URL - cela correspond à l'option publicPath dans vue.config.js et est le chemin de base sur lequel
votre application est déployée.

8. Configuration de la navigation avec Bootstrap et Router View dans Vue


a. Créez le fichier src/views/NavBar.vue, nous définissons ici le composant Bootstrap Navigation
et la directive router-link.

<template>

<nav class="navbar navbar-expand-lg navbar-dark bg-primary">


<button class="navbar-toggler" type="button" data-toggle="collapse" data-
target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-
label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<router-link class="nav-link pr-10" to="/"> Liste </router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" to="/add"> Ajout </router-link>
</li>
</ul>
</div>
</nav>

</template>

Le <router-link> est le composant permettant de faciliter la navigation de l'utilisateur dans une


application vue activée par un routeur.

b. Accédez au fichier src/App.vue, nous définissons ici la directive router-view et nous appelons
le composant NavBar.

<template>
<div>
<NavBar />
<!-- Router view -->
<div class="container mt-5">
<router-view></router-view>
</div>
</div>
</template>

<script>
import NavBar from './views/NavBar.vue'

export default {
name: 'App',
components: {
NavBar
}
}
</script>

Le composant <router-view> est le composant principal responsable du rendu du composant


correspondant pour le chemin fourni.
9. Afficher la liste des données et supprimer les données dans Vue

Maintenant, nous allons afficher les données sous forme d’un tableau à l'aide des classes Bootstrap,
et nous allons faire appel à la requête axios.get() pour restituer les données du serveur à travers
l’adresse de l’api.

<template >
<div>
<h3>Liste des produits</h3>
<table class="table table-striped">
<thead>
<tr>
<th>Référence</th>
<th>Désignation</th>
<th>Marque</th>
<th>Prix d'achat</th>
<th>Prix de vente</th>
<th>Quantité stock</th>
<th>Image</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="p in Produits" :key="p._id">
<td>{{p.reference}}</td>
<td>{{p.designation}}</td>
<td>{{p.marque}}</td>
<td>{{p.prixAchat}}</td>
<td>{{p.prixVente}}</td>
<td>{{p.qtestock}}</td>
<td>
<img :src="require(`../assets/${p.imageartpetitf}`)" alt=""
width="60">
</td>
<td>
<button class="btn btn-block btn-warning"><router-link :to="{name:
'Edit', params: { id: p._id }}">EDIT</router-link> </button>
<button class="btn btn-block btn-danger"
@click="deleteProduct(p._id)">DELETE</button>
</td>

</tr>
</tbody>

</table>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
Produits: []
}
},
created() {
let apiURL = 'http://localhost:3001/api/articles';
axios.get(apiURL).then(res => {
this.Produits = res.data;
}).catch(error => {
console.log(error)
});
},
methods: {
deleteProduct(id){
let apiURL = `http://localhost:3001/api/articles/${id}`;

const indexOfArrayItem = this.Produits.map((e)=> {return e._id


}).indexOf(id);

if (window.confirm("Do you really want to delete?")) {


axios.delete(apiURL).then(() => {
this.Produits.splice(indexOfArrayItem, 1);
}).catch(error => {
console.log(error)
});
}
}
}
}
</script>
<style >
h3{
background-color: rgb(29, 136, 207);
color:aliceblue;
}

</style>

La fonction data() sert à créer des variables réactives qui seront utilisé dans votre application Vue.
Chaque fois qu'une variable réactive est modifié, si elle est affichée ou utilisé dans votre page, Vue la
mettra à jour immédiatement.

Nous pouvons utiliser la directive v-for pour faire le rendu d’une liste d’éléments en nous basant sur
un tableau. La directive v-for utilise une syntaxe spécifique de la forme item in items, où items
représente le tableau source des données et où item est un alias représentant l’élément du tableau en
cours d’itération. Dans notre exemple il s’agit de v-for="p in Produits"

Il est recommandé de fournir une key avec v-for chaque fois que possible, à moins que le contenu itéré
du DOM soit simple ou que vous utilisiez intentionnellement le comportement de base pour un gain
de performance.

Pour afficher une variable réactive ou une expression dans votre page vous devez utiliser les doubles
crochets Vue remplacera le contenu de l'expression par sa valeur {{ p.reference }}

À l’intérieur des structures v-for, nous avons un accès complet aux propriétés de la portée parente. v-
for supporte également un second argument optionnel représentant l’index de l’élément courant.

v-bind Permet d'assigner une expression à un attribut. Vue va remplacer l'expression par sa valeur (ex:
image_url : http://www.exemple.com/car.jpg <img v-bind:src="image_url" /> ou syntaxe raccourci
<img :src="image_url" />

Vue permet de gérer les événements javascript comme click, input, change, etc. Pour ce faire vous
devez utiliser la directive v-on: suivit du nom de l'évènement.

v-on:click Permet d'exécuter du code sur le click d'un élément

<button v-on:click="deleteLivre(liv.id)">Delete</button>

ou syntaxe raccourci

<button @click="deleteLivre(liv.id)">Delete</button>

La méthode appelée (deleteLivre ) est développée dans la partie methods dans le script.

created est appelé de manière synchrone après la création de l'instance. A ce stade, l'instance a fini de
traiter les options, ce qui signifie que les éléments suivants ont été configurés : observation des
données, propriétés calculées, méthodes, rappels watch/event. Cependant, la phase de montage n'a
pas encore commencé et la propriété $el ne sera pas encore disponible.

Résultat obtenu :
10. On va créer le contenu du composant « AjoutProduit.vue ». Ce composant est appelé lorsqu’on
clique sur le lien « Ajout » du menu.

Les listes correspondantes à la catégorie et la sous catégories sont alimentées à partir de la base de
données. La deuxième liste changera en fonction du choix de la catégorie.

La méthode de post Axios prend l'API REST et envoie la requête POST au serveur. Il crée les données
des livres que nous ajoutons dans JSON database.

<template lang="">

<form @submit.prevent="ajouterproduit">
<div class="form-group">
<input type="text" class="form-control" placeholder="reference" v-
model="reference">
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="designation" v-
model="designation">
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="marque" v-
model="marque">
</div>
<div class="form-group">
<input type="number" class="form-control" placeholder="prixAchat" v-
model="prixAchat">
</div>
<div class="form-group">
<input type="number" class="form-control" placeholder="prixVente" v-
model="prixVente">
</div>
<div class="form-group">
<input type="number" class="form-control" placeholder="qtestock" v-
model="qtestock">
</div>
<div class="form-group">
Catégories<select class="form-control" v-model="categorie"
@change="getscategories($event)" >
<option v-for="c in Categories" :key="c._id"
:value=c._id>{{c.nomcategorie}}</option>
</select>
</div>
<div class="form-group">
Sous Catégories<select class="form-control" v-model="scategorie">
<option v-for="sc in Scategories" :key="sc._id"
:value=sc._id>{{sc.nomscategorie}}</option>
</select>
</div>
<div class="form-group">
<input type="file" class="form-control"
placeholder="Image" @change="onFileChange">
</div>
<button type="submit" class="btn btn-block btn-primary">Ajouter
Produit</button>
</form>

</template>

<script>
import axios from 'axios'
export default {
data() {
return {
id:"",
reference: "",
designation: "",
marque: "",
prixAchat: "",
prixVente: "",
qtestock: "",
imageartpetitf: "",
categorie:"",
scategorie:"",
Categories:[],
Scategories:[]
}
},
methods: {

ajouterproduit(){
const pr = {
reference:this.reference,
designation:this.designation,
marque:this.marque,
prixAchat:this.prixAchat,
prixVente:this.prixVente,
qtestock:this.qtestock,
categorieID:this.categorie,
scategorieID:this.scategorie,
imageartpetitf:this.imageartpetitf
}
axios.post("http://localhost:3001/api/articles",pr)
.then(() => {
this.$router.push('/')})
.catch(error => {
this.errorMessage = error.message;
console.error("There was an error!", error);})

},

onFileChange(e) {
this.imageartpetitf = "images/" + e.target.files[0].name;
},

getscategories(event){
let categ=event.target.value;
console.log(categ)
axios.get('http://localhost:3001/api/scategories/cat/'+categ).th
en(res => {
this.Scategories = res.data;
}).catch(error => {
console.log(error)
});
}

},
created() {
axios.get('http://localhost:3001/api/categories').then(res => {
this.Categories = res.data;
}).catch(error => {
console.log(error)
});
},
}
</script>
<style lang="">

</style>

v-model permet de lier la valeur d'un champs de saisie avec une variable. Si vous avez modifié l'un ou
l'autre Vue mettra à jour automatiquement l'autre. Du coup, la variable et le champ de saisie auront
toujours la même valeur. Il s’agit du databiding.

La méthode onFileChange permet de récupérer le contenu du champ file correspondant à l’image


sélectionnée. Puisque le databiding ne marche pas avec ce type file.

Résultat obtenu :
11. Par la suite on va créer le contenu du composant « EditProduit.vue » qui permettra de mettre
à jour les données dans la base.

<template lang="">

<form @submit.prevent="modifierproduit">
<div class="form-group">
<input type="text" class="form-control" placeholder="reference" v-
model="Produit.reference">
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="designation" v-
model="Produit.designation">
</div>
<div class="form-group">
<input type="text" class="form-control" placeholder="marque" v-
model="Produit.marque">
</div>
<div class="form-group">
<input type="number" class="form-control" placeholder="prixAchat" v-
model="Produit.prixAchat">
</div>
<div class="form-group">
<input type="number" class="form-control" placeholder="prixVente" v-
model="Produit.prixVente">
</div>
<div class="form-group">
<input type="number" class="form-control" placeholder="qtestock" v-
model="Produit.qtestock">
</div>
<div class="form-group">
Catégories<select class="form-control" v-model="Produit.categorie"
@change="getscategories($event)" >
<option v-for="c in Categories" :key="c._id"
:value=c._id>{{c.nomcategorie}}</option>
</select>
</div>
<div class="form-group">
Sous Catégories<select class="form-control" v-model="Produit.scategorie">
<option v-for="sc in Scategories" :key="sc._id"
:value=sc._id>{{sc.nomscategorie}}</option>
</select>
</div>
<div class="form-group">
<input type="file" class="form-control"
placeholder="Image" @change="onFileChange">
</div>
<button type="submit" class="btn btn-block btn-primary">Modifier
Produit</button>
</form>

</template>

<script>
import axios from 'axios'
export default {
data() {
return {
Categories:[],
Scategories:[],
Produit:{}
}
},
methods: {

modifierproduit(){
let apiURL =
`http://localhost:3001/api/articles/${this.$route.params.id}`;
axios.put(apiURL, this.Produit).then(() => {
this.$router.push('/')})
.catch(error => {
this.errorMessage = error.message;
console.error("There was an error!", error);})

},

onFileChange(e) {
this.Produit.imageartpetitf = "images/" + e.target.files[0].name;
},

getscategories(event){
let categ=event.target.value;
console.log(categ)
axios.get('http://localhost:3001/api/scategories/cat/'+categ).th
en(res => {
this.Scategories = res.data;
}).catch(error => {
console.log(error)
});
}

},
created() {
axios.get('http://localhost:3001/api/categories').then(res => {
this.Categories = res.data;
}).catch(error => {
console.log(error)
});

let apiURL =
`http://localhost:3001/api/articles/${this.$route.params.id}`;

axios.get(apiURL).then((res) => {
this.Produit = res.data;
})
},
}
</script>
<style lang="">

</style>
Ici, nous devons faire face à deux choses, et nous devons rendre les données lorsque l'utilisateur
atterrit sur le composant des détails de l'étudiant et faire la demande de publication pour mettre à
jour les informations sur le serveur.

Pour obtenir l'identifiant actuel à partir de l'URL existante, utilisez this.$route.params.id vue API.
Utiliser $route dans vos composants crée un couplage fort à la route.

En utilisant la même API, nous pouvons obtenir l'identifiant de l'URL et mettre à jour les valeurs de la
base de données en utilisant l'API.

$route / this.$route est un object global qui contient des informations sur la route actuelle:

- name
- fullPath
- path
- query
- params

Ces propriétés sont injectées dans chacun des composants enfants, en passant l'instance du routeur à
l'application racine de Vue en tant qu'option router.

$router est l'instance du routeur.

$route est la Route actuellement active. C'est une propriété en lecture seule et ses propriétés sont
immutables, mais elles restent malgré tout observables

Résultat obtenu :
12. Améliorer l’affichage de la liste avec datatable
a. Installer datatable
npm install --save datatables.net-dt

b. Installer jQuery
npm install jquery --save

c. Modifier le code de ListComponent.vue

<template >
<div>
<h3>Liste des produits</h3>
<table class="table table-striped" id="example">
<thead>
<tr>
<th>Référence</th>
<th>Désignation</th>
<th>Marque</th>
<th>Prix d'achat</th>
<th>Prix de vente</th>
<th>Quantité stock</th>
<th>Image</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="p in Produits" :key="p._id">
<td>{{p.reference}}</td>
<td>{{p.designation}}</td>
<td>{{p.marque}}</td>
<td>{{p.prixAchat}}</td>
<td>{{p.prixVente}}</td>
<td>{{p.qtestock}}</td>
<td>
<img :src="require(`../assets/${p.imageartpetitf}`)" alt=""
width="60">
</td>
<td>
<button class="btn btn-block btn-warning"><router-link :to="{name:
'Edit', params: { id: p._id }}">EDIT</router-link> </button>
<button class="btn btn-block btn-danger"
@click="deleteProduct(p._id)">DELETE</button>
</td>

</tr>
</tbody>

</table>
</div>
</template>
<script>
import axios from "axios";
//Bootstrap and jQuery libraries
import 'bootstrap/dist/css/bootstrap.min.css';
import 'jquery/dist/jquery.min.js';
//Datatable Modules
import "datatables.net-dt/js/dataTables.dataTables"
import "datatables.net-dt/css/jquery.dataTables.min.css"
import $ from 'jquery';

export default {
data() {
return {
Produits: []
}
},
created() {
let apiURL = 'http://localhost:3001/api/articles';
axios.get(apiURL).then(res => {
this.Produits = res.data;
$(function() {$('#example').DataTable();});
}).catch(error => {
console.log(error)
});
},
methods: {
deleteProduct(id){
let apiURL = `http://localhost:3001/api/articles/${id}`;
const indexOfArrayItem = this.Produits.map((e)=> {return e._id
}).indexOf(id);

if (window.confirm("Do you really want to delete?")) {


axios.delete(apiURL).then(() => {
this.Produits.splice(indexOfArrayItem, 1);
}).catch(error => {
console.log(error)
});
}
}
}
}
</script>
<style >
h3{
background-color: rgb(29, 136, 207);
color:aliceblue;
}

</style>

Résultat obtenu :

Vous aimerez peut-être aussi