Laravel - Vue.js : Uploader un fichier avec une barre de progression

Mis à jour il y a 3 ans

Un tutoriel pour mettre en place un système d’upload de fichier avec une barre de progression pour suivre l’avancement de l’upload dans un projet Laravel - Vue.js

Wilo Ahadi

Auteur

Wilo A.

Technologies

Laravel, PHP, HTML, Vue.js
Voir aussi Un tutoriel pour afficher une numérotation continue des éléments d'une collection sur toutes les pages de la pagination dans un projet Laravel. En savoir plus

Introduction

Une barre de progression peut être définie comme un composant qui indique l’état d’avancement d’une opération. Elle peut se présenter de la manière suivante pour une opération accomplie à 78 % :

Barre de progression

Nous voulons voir dans ce guide comment mettre en place un formulaire d’upload de fichier avec une barre de progression pour suivre l’état d’avancement d’envoi d'un fichier vers le serveur en utilisant le framework JavaScript Vue.js dans un projet Laravel.

Vue.js va nous permettre d’envoyer le fichier vers le serveur via une requête Ajax (Asynchronous JavaScript + XML) avec Axios et suivre la progression de l’upload.

Pour mettre en place Vue.js dans Laravel, référez-vous au guide Installer Vue.js dans un projet Laravel. Vous pourrez ensuite vérifier la version de Vue.js et Axios en exécutant la commande npm ls vue axios :

Voir la version de Laravel, Vue.js et Axios

Nous allons monter notre système en commençant par le coté backend Laravel avec les routes et leurs actions puis nous reviendrons au frontend pour le composant Vue.js.

Laravel : Les routes, le contrôleur et la vue d’upload de fichier

Pour faire fonctionner le système d’upload de fichier au niveau du client comme au niveau du serveur, nous avons besoin de :

  1. Deux routes
  2. Un contrôleur
  3. Une vue (template Blade)

1. Les deux routes

Nous allons utiliser les routes suivantes pour le système d'upload :

  1. « /upload » (GET) pour présenter la page du formulaire d’upload de fichier. Elle sera gérée par l’action « show » du contrôleur UploadFileController.php
  2. « /upload » (POST) pour sauvegarder le fichier envoyé au niveau du serveur. Elle sera gérée par l’action « store » du contrôleur UploadFileController.php

Générons le contrôleur UploadFileController.php pour ces actions en exécutant la commande artisan suivante :

php artisan make:controller UploadFileController

Cette commande crée le fichier /app/Http/Controllers/UploadFileController.php que nous allons compléter au point suivant.

Nous pouvons maintenant définir les routes « upload » (GET et POST) au fichier /routes/web.php :

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UploadFileController;

// Afficher le formulaire
Route::get("upload", [ UploadFileController::class, "show" ]);

// Enregistrer le fichier uploadé
Route::post("upload", [ UploadFileController::class, "store" ]);

2. Le contrôleur

Le contrôleur /app/Http/Controllers/UploadFileController.php décrit les actions « show » et « store » de routes « upload » de la manière suivante :

  • « show » retourne la vue /resources/views/show.blade.php qui va présenter le formulaire d’upload avec la barre de progression
  • « store » valide le fichier envoyé au serveur puis l’enregistre dans le répertoire /storage/images/

Le code source du contrôleur /app/Http/Controllers/UploadFileController.php :

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UploadFileController extends Controller
{
	// Affiche le formulaire d'upload
	public function show () {
		return view("show");
	}

	// Valide puis enregistre le fichier envoyé
    public function store (Request $request) {

    	// La validation
    	$this->validate($request, [
    		"picture" => "bail|required|image|max:1024"
    	]);

    	// On enregistre l'image dans le répertoire "images"
    	$chemin = $request->picture->store("images");

    	// On retourne la réponse en JSON avec le chemin de l'image
    	return response()->json([
    		"chemin" => $chemin
    	]);
    }
}

3. La vue (template Blade)

L'action « show » du contrôleur UploadFileController.php retourne la vue /resources/views/show.blade.php (template Blade). Cette vue peut se présenter de la manière suivante en y intégrant le composant Vue <upload-file> que nous allons créer au point suivant :

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel - Vue.js - Upload + Progress bar</title>
    </head>
    <body>
        <div id="app">

            <h1>Laravel - Vue.js : Upload + Barre de progression</h1>

            <!-- Le composant Vue d'upload de fichier -->
            <upload-file></upload-file>
            
        </div>

    <!-- Le script app.js -->
    <script type="text/javascript" src="{{ asset('js/app.js') }}" ></script>
    
    </body>
</html>

Vue.js : Le composant d’upload et la barre de progression

Le composant Vue <upload-file> que nous avons intégré sur la vue show.blade.php va contenir :

  • Le formulaire d'upload de fichier et la barre de progression dans le <template>
  • la logique d'upload et la gestion de la barre de progression dans le <script>.

Créons le fichier UploadFileComponent.vue pour ce composant dans le répertoire /resources/js/components/

Il faut ensuite enregistrer le composant au fichier /resources/js/app.js :

require('./bootstrap');

import Vue from "vue"

// Le composant d'upload de fichier
Vue.component(
	"upload-file",
	require("./components/UploadFileComponent.vue").default
)

// L'instance Vue
const app = new Vue({
	el : "#app"
});

Puis exécuter la commande npm run dev pour compiler les fichiers JavaScript dans /public/js/app.js.

Voici le code source complet (<template> et <script>) du composant /resources/js/components/UploadFileComponent.vue que nous allons commenter juste après :

<template>

	<!-- Le formulaire d'upload avec la référence "upload_form" -->
	<form v-on:submit.prevent="upload" enctype="multipart/form-data" ref="upload_form" >

		<label for="picture" >Séléctionnez une image</label><br>

		<input type="file" id="picture" name="picture" required accept=".jpg,.png,.gif" >

		<!-- La barre de progression -->
		<div>
			<progress v-bind:value="pourcentage" max="100" >{{ pourcentage }} %</progress> 
			</span>{{ (pourcentage > 0) ? pourcentage + ' %' : '' }}
		</div>

		<button type="submit" >Uploader</button>
	</form>

</template>

<script>
export default {

	data () {
		return {
			// Pourcentage pour la barre de progression
			pourcentage : 0
		}
	},

	methods : {

		// Upload l'image
		upload () {

			// 1. L'objet "formData" avec la référence du formulaire
			let formData = new FormData(this.$refs.upload_form)

			// 2.  Si un fichier est sélectionné = il possède un nom
			if (formData.get('picture').name) {

				// 3. La configuration pour la requête Ajax avec axios
				let config = {
					/*
					Lors de la progression de l'upload "onUploadProgress", on met à jour 
					le $pourcentage en divisant les bytes envoyées "progressEvent.loaded" 
					par les bytes attendues "progressEvent.total"
					*/
					onUploadProgress : progressEvent => {
						this.pourcentage = Math.round((progressEvent.loaded * 100) / progressEvent.total)
					} 
				}

				// 4. On envoie la requête Ajax via axios avec les données du formulaire
				axios.post('upload', formData, config)
				.then((data) => {

					// 5. Si l'image est uploadé
					if (data.data.chemin) {
						// On réinitialise le formulaire et le pourcentage
						this.resetForm()
						alert("Image uploadé")
					}					
				})
				.catch((error) => {
					this.resetForm()
					alert(error)
				})

			} else {
				alert("Sélectionnez le fichier à uploader")
			}
		},

		// Réinitialise le formulaire et le pourcentage
		resetForm () {
			this.$refs.upload_form.reset()
			this.pourcentage = 0
		}
	}
}
</script>

La logique du composant Vue /resources/js/components/UploadFileComponent.vue peut se résumer en ces six points :

1. La donnée pourcentage initialisé à « 0 » représente le niveau de progression de l'upload :

data () {
	return {
		pourcentage : 0
	}
},

Sa valeur est liée (data binding) à l'attribut « value » de la barre de progression <progress> :

<progress v-bind:value="pourcentage" max="100" >{{ pourcentage }} %</progress>

2. Lorsqu'on clique sur le bouton « Uploader » après avoir choisi un fichier, le formulaire envoyé est géré par la méthode upload() :

<form v-on:submit.prevent="upload" enctype="multipart/form-data" ref="upload_form" >

3. Les données du formulaire sont récupérées dans la méthode upload() via l'instance FormData à laquelle on transmet la référence du formulaire « upload_form » :

let formData = new FormData(this.$refs.upload_form)

4. On configure la méthode onUploadProgress(progressEvent) de la requête Ajax d'axios pour suivre la progression de l'upload et mettre à jour le pourcentage :

let config = {
	onUploadProgress : progressEvent => {
		this.pourcentage = Math.round((progressEvent.loaded * 100) / progressEvent.total)
	} 
}

5. On envoie la requête XHR avec les données du formulaire et la configuration « onUploadProgress » :

axios.post('upload', formData, config).then((data) => {
	// ...
}).catch((error) => {
	// ...
})

6. Si la requête échoue ou réussit, on réinitialise le formulaire et le pourcentage en appelant la méthode resetForm() :

resetForm () {
	this.$refs.upload_form.reset()
	this.pourcentage = 0
}

Portez-vous bien ! 😉

Cette publication vous a plu ?
Partagez-la avec vos ami(e)s sur les réseaux sociaux.

Wilo Ahadi

Wilo Ahadi, l'auteur

Passionné de l'informatique, je suis spécialiste en techniques des systèmes et réseaux, développeur web et mobile, infographiste et designer, ... J'aime partager mon expérience en formant sur la plateforme Akili School

Voir profil

Commentaires