In this blog post let's go through the steps involved in building a form in Laravel using VueJS that also has a file input field wherein user can attach an image.
This post assumes that you already have an Laravel project setup on your local.
# Setup VueJS
If your project doesn't have VueJS setup, Follow along the steps in this tutorial to get the install the Vue library in the Laravel project Laravel 7 Installation with Vue JS
# Prepare Model, Controller and Migration File
You can skip this step, if are just interested in the Vue code that handles the file upload.
For the demonstration, let's work with an example we will create Posts.
Run the following artisan command to generate a model along with Controller and a Migration file.
php artisan make:model Post -mr
Modify the migration file named create_posts_table
to include three new fields named title, description and picture
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->string('picture');
$table->timestamps();
});
}
Make sure your project is connected to a database, and run the following command to create the tables into your database.
php artisan migrate
Add the following route entry into the web.php file
Route::resource('posts', 'PostController');
Let's modify the code of create
method of the PostController to return the view file.
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('posts.create');
}
# Setting up Vue Component
Let's dive into the details of the Vue Component. Create a new file named PostCreate.vue
inside resources / js / components.
Template
For the simplicity of the demonstration we will be using inline template. Thus I am keeping the template tags inside the component blank
<template>
</template>
Data properties
export default {
name: 'post-create',
data(){
return{
formFields: {
title: null,
description: null,
picture: null
},
}
},
...
Our form is supposed to have three fields named title, description and a picture we have defined an object named formFields that contains the data property for these three fields.
# Form Submission
<post-create inline-template>
<div>
<form @submit.prevent="submitForm">
<div class="form-group">
<label for="title">Post Title</label>
<input type="text" name="title" class="form-control" id="title" placeholder="Enter Post Title" v-model="formData.title">
</div>
<div class="form-group">
<label for="description">Post Description</label>
<textarea name="description" class="form-control" v-model="formData.description"></textarea>
</div>
<div class="form-group">
<label for="description">Picture</label>
<input type="file" name="picture" class="form-control-file" id="picture" @change="onFileChange">
</div>
<div class="form-group">
<input type="submit" class="btn btn-success" />
</div>
</form>
</div>
</post-create>
We have used the inline-template for our component. We have used the v-bind directive of VueJS to bind different property of the instance to the form.
Notice that we have used two event listeners attached to the form. One is on the change of the file input on which we call the onFileChange
method of the instance and another on the form submit event on which we call the submitForm
method of the instance.
onFileChange method
onFileChange(event){
this.formFields.picture = event.target.files[0];
}
Once user selects the image in the file input it triggers the onFileChange method and this is where we get the file from the target input and assign it to the data property named picture
.
submitForm(){
let formData = new FormData();
formData.append("picture", this.formFields.picture);
formData.append("title", this.formFields.title);
formData.append("description", this.formFields.description);
axios.post('/posts', formData1)
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
});
},
Notice that instead of directly sending the formFields object to the axios, we are using a Javascript object named FormData. We append all the fields inside formData and then send the formData object to the axios.
The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to “multipart/form-data”.
On the backend (i.e. Laravel ) let's modify the store method of the PostController to handle the request sent from the frontend.
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required',
'description' => 'required',
'picture' => 'required'
]);
$request->file('picture')->store('pictures');
Post::create($validatedData);
return ['message' => 'Post Created'];
}
# Preview Image
How about showing a little thumbnail preview to the user on the webpage of the image he has selected for uploading. We can achieve this by using the FileReader javascript API.
Introduce two new data properties to the Vue Component
imagePreview: null,
showPreview: false,
imagePreview will be used to store the actual image and showPreview will be used to determine weather to show the image preview.
Let's modify the onFileChange method to invoke a new FileReader object and also attach a load event to the reader so that it knows when to show the image.
onFileChange(event){
/*
Set the local file variable to what the user has selected.
*/
this.formData.picture = event.target.files[0];
/*
Initialize a File Reader object
*/
let reader = new FileReader();
/*
Add an event listener to the reader that when the file
has been loaded, we flag the show preview as true and set the
image to be what was read from the reader.
*/
reader.addEventListener("load", function () {
this.showPreview = true;
this.imagePreview = reader.result;
}.bind(this), false);
/*
Check to see if the file is not empty.
*/
if( this.formData.picture ){
/*
Ensure the file is an image file.
*/
if ( /\.(jpe?g|png|gif)$/i.test( this.formData.picture.name ) ) {
console.log("here");
/*
Fire the readAsDataURL method which will read the file in and
upon completion fire a 'load' event which we will listen to and
display the image in the preview.
*/
reader.readAsDataURL( this.formData.picture );
}
}
}
Add the following html just after the file input in your form.
<img v-bind:src="imagePreview" width="100" height="100" v-show="showPreview"/>
We have used the v-bind directive to change the image source just after user selects the file.
That's all about showing the image preview and uploading file to the form in VueJS.