Ajax, Axios, Vue.js et Element UI : Guide complet du développement frontend

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 :

  1. Analyse des besoins à partir des documents produit
  2. Définition de l'API dans un document partagé
  3. Développement parallèle frontend et backend
  4. Tests unitaires de chaque côté
  5. 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 ui ouvre 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.

Étiquettes: AJAX axios Vue.js Element UI Vue Router

Publié le 28 juillet à 16h26