File upload with Laravel and VueJS

@endif </div> </div> </div> </div></div>@endsectionBasically our index will get all the files saved in the database and list them in an HTML table.

In the controller app/Http/Controllers/FileEntriesController.

php create the index method as follows:public function index() { $files = FileEntry::all(); return view('files.

index', compact('files'));}Here we are taking all the files saved in the database and writing to the files variable ($files = FileEntry::all()), and then returning it to our view (return view(‘files.

index’, compact(‘files’))).

Finally, we’ll need to register our route to be accessible via the URL.

In the routes/web.

php file add the following code snippet:Route::group(['middleware' => 'auth'], function () { Route::get('files', 'FileEntriesController@index');});The Route::group([‘middleware’ => ‘auth’], function () {}) section will protect our new view, allowing only authenticated users access.

Inside it, we add our “get”, Route::get(‘files’, ‘FileEntriesController@index’), where the first parameter is the name of the route, and the second is the method in the controller that will reference this route.

Accessing the url in the browser, the following screen should appear:Index screen of the filesWorking with VueJSNow it's time to make our vue component that will assist us in our uploading files, in addition to making it practical, beautiful and simple for our end-user.

Before starting with our development in VueJS, we must install the necessary dependencies.

To do this, just enter the following command at the root of our project:npm installWait a while.

It'll download and install all the dependencies in the project.

Note: When deploying to the web, you must run the command again on the server.

Now, create the file resources/assets/js/components/UploadFiles.

vue.

Componentes Vue have the following basic structure:<template> </template><script> export default { }</script><style scoped></style>The <template></template> tags delimit the HTML code that will be displayed in our view.

The <script></script> tags define where the programming will go.

The <style></style> tags define where the custom CSS for our component will go.

The latter is optional.

TemplateIn our file, inside the <template></template> tags, add the following code:<template> <div class="container"> <div class="large-12 medium-12 small-12 filezone"> <input type="file" id="files" ref="files" multiple v-on:change="handleFiles()"/> <p> Drop your files here <br>or click to search </p> </div> <div v-for="(file, key) in files" class="file-listing"> <img class="preview" v-bind:ref="'preview'+parseInt(key)"/> {{ file.

name }} <div class="success-container" v-if="file.

id > 0"> Success </div> <div class="remove-container" v-else> <a class="remove" v-on:click="removeFile(key)">Remove</a> </div> </div> <a class="submit-button" v-on:click="submitFiles()" v-show="files.

length > 0">Submit</a> </div></template>Now we have some points to highlight:InputFirst of all, in <input> we have the attribute “ref”, which names the input, so that we can access it and work in VueJS.

Next, we have the “handleFiles” method that is executed in v-on:change whenever there are changes in the field.

Preview and RemoveThe following is a for loop in a <div>, which will be shown for each file mapped by the files variable.

Within the div we have the preview image, which is shown according to the index of the file in the array, followed by the file name, and two divs: Success and Remove.

These two divs are bound by v-if and v-else, where if the file has not yet been submitted, the option to remove the file will be visible.

If the file is submitted, the file can no longer be removed, and only the “Success” information will be displayed, indicating that the file was sent correctly.

SubmitFinally, we create the submit button with the “submitFiles” method that is executed whenever it is clicked (v-on: click).

This button is shown only while there are files to submit, according to the Vue v-show.

ScriptFirst of all, let’s instantiate the variables that we will work on:data() { return { files: [] }}files is an array that will store the uploaded files.

It starts empty.

Next, we have our methods, which should be within:methods: { // your methods here}The first is handleFiles():handleFiles() { let uploadedFiles = this.

$refs.

files.

files; for(var i = 0; i < uploadedFiles.

length; i++) { this.

files.

push(uploadedFiles[i]); } this.

getImagePreviews();},It is responsible for receiving all the files added to the input and arranging them in the file array.

At the end, it will call the following method, getImagePreviews, which will show the preview of the files.

getImagePreviews(){ for( let i = 0; i < this.

files.

length; i++ ){ if ( /.

(jpe?g|png|gif)$/i.

test( this.

files[i].

name ) ) { let reader = new FileReader(); reader.

addEventListener("load", function(){ this.

$refs['preview'+parseInt(i)][0].

src = reader.

result; }.

bind(this), false); reader.

readAsDataURL( this.

files[i] ); }else{ this.

$nextTick(function(){ this.

$refs['preview'+parseInt(i)][0].

src = '/img/generic.

png'; }); } }},Basically, it walk through the entire array of files and first checks to see if it is an image.

If affirmative, it generates a preview of that file and adds it to be displayed in the respective file div.

If not, it displays a default image as preview.

The image I am using is available below.

Save it to public public/img/generic.

png.

Default file imageThe following is the method of removing files:removeFile( key ){ this.

files.

splice( key, 1 ); this.

getImagePreviews();},It just removes the chosen file from the array of files and updates the previews of the images.

Finally, we have the submit method:submitFiles() { for( let i = 0; i < this.

files.

length; i++ ){ if(this.

files[i].

id) { continue; } let formData = new FormData(); formData.

append('file', this.

files[i]); axios.

post('/' + this.

post_url, formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).

then(function(data) { this.

files[i].

id = data['data']['id']; this.

files.

splice(i, 1, this.

files[i]); console.

log('success'); }.

bind(this)).

catch(function(data) { console.

log('error'); }); }}It walk through the array of files, verifies that the file has not been sent (if it has an id, has already been sent) and sends them one by one.

If successful, it updates array files to be reloaded in the template.

Let’s take a closer look at what happens here:First we create the form with let formData = new FormData().

Next we add the file to the form to be sent: formData.

append(‘file’, this.

files[i]).

Now we send the form through a request using axios.

For this, we will use the “post” method.

In it we have three parameters.

The first is the destination url, which in our case is “/files/upload-file”.

The second parameter is what we want to send, which in this case is the formData form that we created earlier.

In the third parameter we add the header, which contains ‘Content-Type’: ‘multipart/form-data’ so that it is possible to send files through the form.

Finally, we have the callback methods for success and error.

Note that no success (then) will be taking the id of the newly uploaded file and updating the array files with the new information.

StyleI will not delve into the CSS for the article does not get too big, but basically it hides the input from the files and instead, renders the dashed box where the files can be dragged and dropped.

<style scoped> input[type="file"]{ opacity: 0; width: 100%; height: 200px; position: absolute; cursor: pointer; } .

filezone { outline: 2px dashed grey; outline-offset: -10px; background: #ccc; color: dimgray; padding: 10px 10px; min-height: 200px; position: relative; cursor: pointer; } .

filezone:hover { background: #c0c0c0; } .

filezone p { font-size: 1.

2em; text-align: center; padding: 50px 50px 50px 50px; } div.

file-listing img{ max-width: 90%; } div.

file-listing{ margin: auto; padding: 10px; border-bottom: 1px solid #ddd; } div.

file-listing img{ height: 100px; } div.

success-container{ text-align: center; color: green; } div.

remove-container{ text-align: center; } div.

remove-container a{ color: red; cursor: pointer; } a.

submit-button{ display: block; margin: auto; text-align: center; width: 200px; padding: 10px; text-transform: uppercase; background-color: #CCC; color: white; font-weight: bold; margin-top: 20px; }</style>Registering the component and compilingNow that we have completed our Vue component, we must register the name that we will use in our view to call it.

To do this open the resources/assets/js/app.

js file and add the following after the example component:Vue.

component('upload-files', require('.

/components/UploadFiles.

vue'));You can assign the name you want.

In this tutorial, I chose to choose “upload-files”.

Once coding is complete, we must now compile our Vue project.

At the root of your Laravel project, run the following command:npm run watchWe could use run dev or run build, but while we are still developing, I prefer to use the watch, because with every change made in the code, it will be recompiled without having to run again.

Submitting filesNow that we have our component developed, we can now create the file upload view in Laravel.

To do this, create the file resources/views/files/create.

blade.

php and add the following code:@extends('layouts.

app')@section('content')<div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">Add files</div> <div class="card-body"> <upload-files></upload-files> </div> </div> </div> </div></div>@endsectionNotice that we have added our new <upload-files></upload-files> component.

We now only need to create the respective method in the controller and its route.

In the FileEntriesController add the following method:public function create() { return view('files.

create');}And in the routes/web.

php add the route for this view, and also the route that will receive the POST request of Axios:Route::get('files/create', 'FileEntriesController@create');Route::post('files/upload-file', 'FileEntriesController@uploadFile');By testing our new feature, it looks like this:Upload screen with two files to submitOur script is now submitting the files and saving them to the database.

If we return to the view index, the file listing will be displayed:File listIf you want to download the file when click on name, just add the following route:Route::get('files/{path_file}/{file}', function($path_file = null, $file = null) { $path = storage_path().

'/files/uploads/'.

$path_file.

'/'.

$file; if(file_exists($path)) { return Response::download($path); }});And at the filename:<a href="{{ url('files/'.

$file->path.

'/'.

$file->filename) }}">{{ $file->filename }}</a>Bonus: Adding Files to Other ModelsOur component is working perfectly and saving the files in the respective files table, but generally we want to associate the files sent to some other model such as personal documents of the user.

By adding a small change to our code, we can leave the newly created component versatile and reused throughout the project.

For this, in our Vue component, we must create some parameters that will be sent through the view.

They are necessary because for each model, the address where the files will be sent by Axios may change, and also the “name” of the input may change.

Open resources/assets/js/components/UploadFiles.

vue and make the following bold change in the template:<template> <div class="container"> <div class="large-12 medium-12 small-12 filezone"> <input type="file" id="files" ref="files" multiple v-on:change="handleFiles()"/> <p> Drop your files here <br>or click to search </p> </div> <div v-for="(file, key) in files" class="file-listing"> <img class="preview" v-bind:ref="'preview'+parseInt(key)"/> {{ file.

name }} <div class="success-container" v-if="file.

id > 0"> Success <input type="hidden" :name="input_name" :value="file.

id"/> </div> <div class="remove-container" v-else> <a class="remove" v-on:click="removeFile(key)">Remove</a> </div> </div> <a class="submit-button" v-on:click="submitFiles()" v-show="files.

length > 0">Submit</a> </div></template>Notice that we have added an input that will only be displayed if the file has an id, ie it was sent correctly.

This input is named “input_name” and its name will be sent through the VueJS props.

As the file upload URL may change too, depending on the model, we will create a props called “post_url” to receive it.

To do this, add the following bold code at the beginning of the script:<script> export default { props: ['input_name', 'post_url'], data() { return { files: [] } },Now, we’ll add the URL prop in our Axios call.

The newly uploaded file id was previously saved, so it does not change.

The submitFiles method should look like this:submitFiles() { for( let i = 0; i < this.

files.

length; i++ ){ if(this.

files[i].

id) { continue; } let formData = new FormData(); formData.

append('file', this.

files[i]); axios.

post('/' + this.

post_url, formData, { headers: { 'Content-Type': 'multipart/form-data' } } ).

then(function(data) { this.

files[i].

id = data['data']['id']; this.

files.

splice(i, 1, this.

files[i]); console.

log('success'); }.

bind(this)).

catch(function(data) { console.

log('error'); }); }}Now that we add these two props in our Vue component, whenever we use the created component, we should pass them as a parameter as follows:<upload-files :input_name="'users[]'" :post_url="'files/upload-file'"></upload-files>Where, in this example, our custom input will be users[] and our submission URL will be files/upload-file.

It is now ready to be used in any form, simply informing the name of the input (as the upload can be of several files, we should use brackets [] to indicate array), and which URL will be submitted to the files.

For each successfully uploaded file, your IDs will be stored in an input to be sent on the form and thus be associated with your model, as highlighted in the image below:To treat the result of the form in the submission, you should do something like this:foreach($input['users'] as $file) { //some action to save the file_id}That’s all.

Hope this helps.

Feel free to ask your questions in the comments below.

Next I make available the project link of this tutorial in GITHUB for those who want to visualize.

Project on GitHubMore where this came fromThis story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.

Follow our publication to see more product & design stories featured by the Journal team.

.

. More details

Leave a Reply