Fondamentaux d'Ajax
Ajax (Asynchronous JavaScript And XML) permet à une page web d'échanger des données avec un serveur de manière asynchrone, c'est-à-dire sans recharger la page entière. Cette technique est essentielle pour créer des interfaces réactives. Par exemple, la suggestion automatique dans un moteur de recherche ou la vérification instantanée de la disponibilité d'un nom d'utilisateur reposent sur Ajax.
Deux concepts clés distinguent les requêtes synchrones des requêtes asynchrones :
- Requête synchrone : le navigateur se bloque pendant que le serveur traite la demande. L'utilisateur ne peut rien faire d'autre jusqu'à réception de la réponse.
- Requête asynchrone : le navigateur reste interactif durant le traitement serveur. L'utilisateur peut continuer à naviguer sur la page.
Ajax natif
Voici un exemple d'utilisation de l'objet XMLHttpRequest pour effectuer une requête Ajax native :
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Ajax natif</title>
</head>
<body>
<button onclick="chargerDonnees()">Charger les données</button>
<div id="zoneResultat"></div>
<script>
function chargerDonnees() {
// 1. Instancier l'objet de communication
const req = new XMLHttpRequest();
// 2. Configurer la requête
req.open('GET', 'https://mock.apifox.cn/m1/3128855-0-default/emp/list');
req.send();
// 3. Gérer la réponse via un callback
req.onreadystatechange = function() {
if (req.readyState === 4 && req.status === 200) {
document.getElementById('zoneResultat').innerHTML = req.responseText;
}
};
}
</script>
</body>
</html>
Axios : une alternative simplifiée
Axios encapsule l'API Ajax native dans une interface plus expressive et concise. Il s'utilise en important le fichier JavaScript correspondant, puis en appelant axios() avec un objet de configuration.
<script src="js/axios-0.18.0.js"></script>
<script>
// Requête GET
axios({
method: 'get',
url: 'https://mock.apifox.cn/m1/3128855-0-default/emp/list'
}).then(reponse => {
console.log(reponse.data);
});
// Requête POST
axios({
method: 'post',
url: 'https://mock.apifox.cn/m1/3128855-0-default/emp/deleteById',
data: 'id=1'
}).then(reponse => {
console.log(reponse.data);
});
</script>
Axios propose également des alias pour chaque verbe HTTP :
axios.get(url)axios.post(url, donnees)axios.put(url, donnees)axios.delete(url)
axios.get('https://mock.apifox.cn/m1/3128855-0-default/emp/list')
.then(reponse => console.log(reponse.data));
axios.post('https://mock.apifox.cn/m1/3128855-0-default/emp/deleteById', 'id=1')
.then(reponse => console.log(reponse.data));
Exemple combiné Vue et Axios
Cet exemple charge dynamiquement une liste d'employés dans un tableau lors du chargement de la page :
<div id="racine">
<table border="1" cellspacing="0" width="60%">
<tr>
<th>N°</th>
<th>Nom</th>
<th>Photo</th>
<th>Sexe</th>
<th>Poste</th>
<th>Date d'embauche</th>
<th>Dernière mise à jour</th>
</tr>
<tr align="center" v-for="(employe, index) in listeEmployes">
<td>{{ index + 1 }}</td>
<td>{{ employe.name }}</td>
<td><img :src="employe.image" width="70px" height="50px"></td>
<td>
<span v-if="employe.gender === 1">Homme</span>
<span v-if="employe.gender === 2">Femme</span>
</td>
<td>{{ employe.job }}</td>
<td>{{ employe.entrydate }}</td>
<td>{{ employe.updatetime }}</td>
</tr>
</table>
</div>
<script src="js/vue.js"></script>
<script src="js/axios-0.18.0.js"></script>
<script>
new Vue({
el: '#racine',
data: {
listeEmployes: []
},
mounted() {
axios.get('https://mock.apifox.cn/m1/3128855-0-default/emp/list')
.then(reponse => {
this.listeEmployes = reponse.data.data;
});
}
});
</script>
Architecture frontend-backend séparée
Dans une approche monolithique traditionnelle, le code frontend et backend cohabitent dans un même projet. Cela pose plusieurs problèmes : communication coûteuse entre équipes, responsabilités floues, déploiement global obligatoire même pour une modification mineure de l'interface.
L'approche moderne sépare les deux : un projet frontend géré par des développeurs spécialisés, un projet backend géré par des développeurs serveur. Les deux communiquent via des requêtes HTTP. Pour coordonner le travail, une specification d'API (interface document) définit les endpoints, paramètres et structures de réponses.
Le flux de travail typique :
- Analyse des besoins à partir des documents produit
- Définition de l'API dans un document partagé
- Développement parallèle frontend et backend
- Tests unitaires de chaque côté
- Intégration et tests de bout en bout
YApi : gestion d'API
YApi est une plateforme de gestion d'API open source qui centralise la documentation des interfaces et fournit un service de mock. Les fonctionnalités principales :
- Création et organisation d'interfaces par projet et catégorie
- Définition des paramètres d'entrée et de la structure de réponse
- Génération automatique d'un endpoint mock pour tester le frontend sans backend
Industrialisation du frontend
Le développement frontend moderne repose sur l'ingénierie logicielle : modularité du code, composants réutilisables, structure de projet standardisée, automatisation du build. Vue CLI est l'outil officiel pour scaffolder un projet Vue structuré.
Préparation de l'environnement
Vue CLI nécessite Node.js. Après installation, deux méthodes permettent de créer un projet :
- Ligne de commande :
vue create mon-projet - Interface graphique :
vue uiouvre un assistant web sur le port 8000
Structure d'un projet Vue
mon-projet/
├── node_modules/
├── public/
│ └── index.html
├── src/
│ ├── assets/
│ ├── components/
│ ├── router/
│ │ └── index.js
│ ├── views/
│ ├── App.vue
│ └── main.js
├── package.json
└── vue.config.js
Cycle de vie d'un projet Vue
Le fichier public/index.html est minimal. Il référence main.js, qui instancie l'application Vue et la monte sur l'élément #app. Le rendu est délégué au composant racine App.vue.
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App)
}).$mount('#app');
Un composant .vue se compose de trois sections :
<template>: structure HTML<script>: logique JavaScript<style>: styles CSS
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return { message: 'Bonjour le monde' };
}
}
</script>
<style></style>
Pour lancer le serveur de développement, utilisez npm run serve dans le terminal. Le projet démarre par défaut sur le port 8080. Pour modifier ce port, éditez vue.config.js :
module.exports = {
devServer: {
port: 7000
}
};
Element UI : bibliothèque de composants
Element UI, développée par l'équipe frontend d'Ele.me, est une bibliothèque de composants Vue conçue pour construire rapidement des interfaces d'administration. Elle fournit des boutons, tableaux, formulaires, boîtes de dialogue, pagination, etc.
Installation et intégration
npm install element-ui --save
Dans main.js :
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Composants essentiels
Tableau (el-table)
<el-table :data="donneesTab" style="width: 100%">
<el-table-column prop="date" label="Date" width="180"></el-table-column>
<el-table-column prop="nom" label="Nom" width="180"></el-table-column>
<el-table-column prop="adresse" label="Adresse"></el-table-column>
</el-table>
<script>
export default {
data() {
return {
donneesTab: [
{ date: '2024-01-15', nom: 'Alice', adresse: 'Paris, France' },
{ date: '2024-02-20', nom: 'Bob', adresse: 'Lyon, France' }
]
};
}
}
</script>
Attributs clés : data (modèle de données), prop (champ lié), label (titre de colonne), width (largeur).
Pagination (el-pagination)
<el-pagination
@size-change="gererChangementTaille"
@current-change="gererChangementPage"
background
layout="sizes, prev, pager, next, jumper, total"
:total="1000">
</el-pagination>
<script>
export default {
methods: {
gererChangementTaille(val) {
console.log(`Taille de page : ${val}`);
},
gererChangementPage(val) {
console.log(`Page courante : ${val}`);
}
}
}
</script>
Boîte de dialogue (el-dialog)
<el-button type="text" @click="fenetreVisible = true">Ouvrir la boîte</el-button>
<el-dialog title="Détails" :visible.sync="fenetreVisible">
<el-table :data="donneesDialog">
<el-table-column property="date" label="Date" width="150"></el-table-column>
<el-table-column property="nom" label="Nom" width="200"></el-table-column>
<el-table-column property="adresse" label="Adresse"></el-table-column>
</el-table>
</el-dialog>
<script>
export default {
data() {
return {
fenetreVisible: false,
donneesDialog: [
{ date: '2024-03-01', nom: 'Charlie', adresse: 'Marseille, France' }
]
};
}
}
</script>
L'attribut :visible.sync synchronise la visibilité avec une variable réactive.
Formulaire (el-form)
<el-dialog title="Nouvel enregistrement" :visible.sync="dialogFormulaire">
<el-form ref="formulaireRef" :model="formDonnees" label-width="100px">
<el-form-item label="Nom">
<el-input v-model="formDonnees.nom"></el-input>
</el-form-item>
<el-form-item label="Région">
<el-select v-model="formDonnees.region" placeholder="Choisir">
<el-option label="Zone A" value="zoneA"></el-option>
<el-option label="Zone B" value="zoneB"></el-option>
</el-select>
</el-form-item>
<el-form-item label="Date">
<el-date-picker type="date" v-model="formDonnees.dateChoisie" style="width: 100%;">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="soumettreFormulaire">Valider</el-button>
<el-button @click="dialogFormulaire = false">Annuler</el-button>
</el-form-item>
</el-form>
</el-dialog>
<script>
export default {
data() {
return {
dialogFormulaire: false,
formDonnees: {
nom: '',
region: '',
dateChoisie: ''
}
};
},
methods: {
soumettreFormulaire() {
console.log(JSON.stringify(this.formDonnees));
this.dialogFormulaire = false;
}
}
}
</script>
Cas pratique : système de gestion d'employés
Cet exemple combine layout, formulaire de recherche, tableau, pagination et chargement asynchrone de données.
<template>
<div>
<el-container style="height: 700px; border: 1px solid #eee">
<el-header style="font-size: 40px; background-color: rgb(238, 241, 246)">
Système de gestion intelligent
</el-header>
<el-container>
<el-aside width="230px" style="border: 1px solid #eee">
<el-menu :default-openeds="['1']">
<el-submenu index="1">
<template slot="title">
<i class="el-icon-message"></i>Gestion du système
</template>
<el-menu-item index="1-1">
<router-link to="/dept">Gestion des départements</router-link>
</el-menu-item>
<el-menu-item index="1-2">
<router-link to="/emp">Gestion des employés</router-link>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-main>
<el-form :inline="true" :model="critereRecherche" class="demo-form-inline">
<el-form-item label="Nom">
<el-input v-model="critereRecherche.nom" placeholder="Nom"></el-input>
</el-form-item>
<el-form-item label="Sexe">
<el-select v-model="critereRecherche.sexe" placeholder="Sexe">
<el-option label="Homme" value="1"></el-option>
<el-option label="Femme" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="Période d'embauche">
<el-date-picker
v-model="critereRecherche.periode"
type="daterange"
range-separator="à"
start-placeholder="Début"
end-placeholder="Fin">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="lancerRecherche">Rechercher</el-button>
</el-form-item>
</el-form>
<el-table :data="donneesEmployes">
<el-table-column prop="name" label="Nom" width="180"></el-table-column>
<el-table-column prop="image" label="Photo" width="180">
<template slot-scope="scope">
<img :src="scope.row.image" width="100px" height="70px">
</template>
</el-table-column>
<el-table-column prop="gender" label="Sexe" width="140">
<template slot-scope="scope">
{{ scope.row.gender === 1 ? 'Homme' : 'Femme' }}
</template>
</el-table-column>
<el-table-column prop="job" label="Poste" width="140"></el-table-column>
<el-table-column prop="entrydate" label="Embauche" width="180"></el-table-column>
<el-table-column prop="updatetime" label="Mise à jour" width="230"></el-table-column>
<el-table-column label="Actions">
<el-button type="primary" size="mini">Modifier</el-button>
<el-button type="danger" size="mini">Supprimer</el-button>
</el-table-column>
</el-table>
<el-pagination
@size-change="changementTaille"
@current-change="changementPage"
background
layout="sizes, prev, pager, next, jumper, total"
:total="1000">
</el-pagination>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
donneesEmployes: [],
critereRecherche: {
nom: '',
sexe: '',
periode: []
}
};
},
methods: {
lancerRecherche() {
console.log(this.critereRecherche);
},
changementTaille(val) {
console.log(`Éléments par page : ${val}`);
},
changementPage(val) {
console.log(`Page actuelle : ${val}`);
}
},
mounted() {
axios.get('https://mock.apifox.cn/m1/3128855-0-default/emp/list')
.then(reponse => {
this.donneesEmployes = reponse.data.data;
});
}
}
</script>
<style></style>
Vue Router : navigation entre vues
Vue Router associe les fragments d'URL (hash) à des composants Vue. Il se compose de trois éléments :
- VueRouter : le routeur qui gère la table de correspondance
<router-link>: génère un lien de navigasion (rendu en<a>)<router-view>: emplacement où le composant correspondant est rendu
Définition des routes dans src/router/index.js :
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path: '/emp',
name: 'employes',
component: () => import('../views/tlias/EmpView.vue')
},
{
path: '/dept',
name: 'departements',
component: () => import('../views/tlias/DeptView.vue')
},
{
path: '/',
redirect: '/emp'
}
];
const router = new VueRouter({ routes });
export default router;
Dans App.vue, on place <router-view> là où les composants doivent s'afficher :
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
Build et déploiement avec Nginx
Compilation du projet
Exécutez npm run build pour générer le dossier dist/ contenant les fichiers statiques optimisés.
Déploiement via Nginx
Nginx est un serveur web léger et performant. Après décompresion de l'archive, copiez le contenu de dist/ dans le dossier html/ de Nginx, puis lencez nginx.exe.
Le serveur écoute par défaut sur le port 80. Si celui-ci est occupé, modifiez la directive listen dans conf/nginx.conf :
server {
listen 9090;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
Accédez ensuite à http://localhost:9090 pour visualiser l'application déployée.