Preview File Uploads with Axios and VueJS
Part 4 of 7 in Your Guide To Uploading Files with VueJS and AxiosPreviously, I wrote a quick article about Uploading Files With VueJS and Axios – Server Side Up which should help alleviate some of the pain of dealing with file uploads. Now it’s time to add a few bells and whistles and a little UX. In this tutorial we will work with previewing file uploads and displaying progress using Axios GitHub – axios/axios: Promise based HTTP client for the browser and node.js and VueJS GitHub – vuejs/vue: ? A progressive, incrementally-adoptable JavaScript framework for building UI on the web..
🚨 UPDATE 10/14/2021
We’ve updated all of our code to be Vue 3.0.0 and Axios 0.21.1 compatible. We’ve also added a Github repo where you can download all of our examples and implement them into your project. I’ve also provided a small code update that is explained near the bottom of this tutorial.
Prerequisites
Like in Uploading Files With VueJS and Axios – Server Side Up, I’m going to be using Axios v0.16.2 and VueJS v2.1.0 for doing the front end part of the file uploads. You will be able to handle the file submission in any language you choose.
For previewing files, we will be using the FileReader API: FileReader – Web APIs | MDN. There’s a great overview of the FileReader on this site and how it works. I’ve adapted most of the tutorial from these examples to work with VueJS and Axios.
The FileReader allows us to read the file the user selected as a blob and preview it in the browser. This is mainly for images, but I’m sure it can be adapted for PDFs and other file types viewable by the browser. In this tutorial we will be working with images.
Previewing Single Image Uploads
The first component we will build will allow users to view a preview of a single image they select. I mentioned before, we will only be working with images in this tutorial. The preview uses the FileReader web API and I’m sure it can be adapted to work with PDFs and other formats, but for now that’s out of scope since most use cases are covered with images. We will expand to previewing multiple images selected later on in this tutorial.
First, we need to create a Vue component. Like in the last tutorial, Uploading Files With VueJS and Axios – Server Side Up, will create a component for each feature. If you are using this tutorial for a production form or in an app that requires more fields than just a file upload, you will want to scope those fields as well into the form and use part of the components we build in here. Any questions on this, just ask in the comments section below!
Add FilePreview.vue Component
For this tutorial, I created a simple Vue component named FilePreview.vue
and added the following template:
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>File Preview
<input type="file" id="file" ref="file" accept="image/*" v-on:change="handleFileUpload()"/>
</label>
<img v-bind:src="imagePreview" v-show="showPreview"/>
<button v-on:click="submitFile()">Submit</button>
</div>
</div>
</template>
A few things to note about the template. We have a file input which has a variety of different attributes. The first one we will discuss is the ref="file"
attribute. What this does is make a key that we can reference in our Javascript component that allows us to access what’s been updated. We will referencing the file input like this (discussed in next step):
this.file = this.$refs.file.files[0];
Another attribute is the accept="image/*"
attribute. What this does is limit the upload only to accept images. For the preview functionality we are building, we only want users to upload images so we can show the preview.
Finally, we have a v-on:change="handleFileUpload()"
attribute. What this does is when the user uploads a file and the contents of the input changes, we call our handleFileUpload()
method. This will allow us to read in the file using a FileReader()
and display the image to the user.
The template also contains an img
element. This element is what receives the source of the image the user uploaded. It has two attributes. The first, is the binding of the src
tag with v-bind:src="imagePreview"
. What this does is bind the src
of the image to the variable we set up named imagePreview
(discussed later in the tutorial). The second attribute is v-show="showPreview"
. This determines if we should show the image preview or not. It’s simply there for a quick UX enhancement. We don’t want what looks like a broken image in our preview if nothing has been selected. We will default this to false
so it doesn’t show in the next step. When the user selects a file, we will toggle this to true
.
In the template there is also a button
element. When clicked, it sends the file to be processed by the server with the submitFile()
method.
Add Javascript Data to the Component
We’ve already discussed a few variables we need to make our component function so let’s add those to the <script>
part of our component:
/*
Defines the data used by the component
*/
data(){
return {
file: '',
showPreview: false,
imagePreview: ''
}
},
The first variable is the model for the file we are uploading. This will contain the file and allow us to submit it to the API or wherever we are submitting.
The showPreview
variable determines if we should show the file preview. This will be set to true when the file has been read in and ready to be uploaded. It’s defaulted to false so we don’t show what looks like a broken image right off of the bat.
The imagePreview
is going to be the blob which is a result of the file reader. If you were to have an image that is a placeholder image, you’d set the imagePreview
to the url of the image and the showPreview
to true or remove the flag.
Next, we will be adding some functionality to make this all work!
Add the Handle File Upload Method
Now that we have our data, let’s add the handle file upload method. This will be what we use to set the local file variable to what was uploaded and display the preview. Once the user has selected a file, this method will be called. In our methods
object on our component, add the following method:
methods: {
/*
Handles a change on the file upload
*/
handleFileUpload(){
/*
Set the local file variable to what the user has selected.
*/
this.file = this.$refs.file.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.file ){
/*
Ensure the file is an image file.
*/
if ( /\.(jpe?g|png|gif)$/i.test( this.file.name ) ) {
/*
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.file );
}
}
}
}
This method is where all of the magic happens!
First, we set the local file
variable to the file uploaded in the file input.
/*
Set the local file variable to what the user has selected.
*/
this.file = this.$refs.file.files[0];
Remember when we set the ref="file"
attribute on the file input? This is where it comes into play. We can access what was uploaded to the input by calling the global this.$refs.file
where file
is the name of the reference. It’s like a unique ID that VueJS can use. We then get the first file in the file list. When we do multiple files, the refs
attribute will be used a lot more heavily.
Next, we create a FileReader()
, documented here: FileReader – Web APIs | MDN.
/*
Initialize a File Reader object
*/
let reader = new FileReader();
The FileReader()
will be used to read in the file selected by the user. and display a preview of the image.
Next, we add an event listener to our FileReader()
for load
:
/*
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);
We do this because we want to listen when the file has completed loading and then display it to the user. With our event listener we .bind(this)
which gives us access to our local Vue component’s variables. When the load
event has been called, we show the preview image and we set the image to the result of what was read in.
Finally, we read in the file after a few validity checks:
/*
Check to see if the file is not empty.
*/
if( this.file ){
/*
Ensure the file is an image file.
*/
if ( /\.(jpe?g|png|gif)$/i.test( this.file.name ) ) {
/*
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.file );
}
}
First we check to see if the file is present and not null meaning the user has uploaded a file. Since the method has been called when the file is uploaded, it should be there, but it’s a simple check to ensure we don’t run into issues later on. Next, we confirm the file is an image. We test the file name for a matching image extension. If it’s an image, then we finally run:
/*
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.file );
This will begin reading in the user’s uploaded file and when the file is completed, the load
event that we are listening to will be fired, showing the image preview.
Just like that, we have our file being previewed using VueJS and Axios! Of course I’d add a few styles to the image tag maybe setting a maximum width or height so if a user uploads a massive file it doesn’t take up the screen and all of the UX, but functionally, this part should be complete! I’ll run through the submitting to the server real quick, but for more information check out the previous tutorial: Uploading Files With VueJS and Axios – Server Side Up
Build submitFile() method
This is the last method we need to add to our methods
object. When the user has uploaded a file and clicked the submit button, this method will be called. We will be using the FormData()
FormData – Web APIs | MDN like before to send the file to the server.
First, let’s add our method and I’ll step through the functionality:
/*
Submits the file to the server
*/
submitFile(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('file', this.file);
/*
Make the request to the POST /single-file URL
*/
axios.post( '/file-preview',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
Right off the bat, we initialize our FormData()
object..
/*
Initialize the form data
*/
let formData = new FormData();
This will be all of the data we submit in the POST
request to the endpoint.
Next, we append the local file to the formData
object:
/*
Add the form data we need to submit
*/
formData.append('file', this.file);
We’d also append any other data here that we want to send if we have a larger form.
Next, we make our actual request using Axios:
/*
Make the request to the POST /single-file URL
*/
axios.post( '/file-preview',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
The first parameter in the axios.post()
method is the url that we will be submitting to. In my test environment, I set up an endpoint on an API named /file-preview
that accepts a POST
request. The second parameter is the data we are sending to the server. In this case, it’s the formData
variable. The third parameter is all of the configuration for the request. This is extremely important since we need to add the Content Type header: 'Content-Type': 'multipart/form-data'
. This alerts the server that we have multipart form data and could be sending files. I then have two callbacks. One for success and a catch for failure. They do nothing except print to the console, but if you are developing on production maybe you’d like to reset the data or display a notification. Whatever works! Our component should be complete now and you can display a file upload preview to your users!
Our final FilePreview.vue
should look like:
<style>
div.container img{
max-width: 200px;
max-height: 200px;
}
</style>
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>File Preview
<input type="file" id="file" ref="file" accept="image/*" v-on:change="handleFileUpload()"/>
</label>
<img v-bind:src="imagePreview" v-show="showPreview"/>
<button v-on:click="submitFile()">Submit</button>
</div>
</div>
</template>
<script>
export default {
/*
Defines the data used by the component
*/
data(){
return {
file: '',
showPreview: false,
imagePreview: ''
}
},
methods: {
/*
Submits the file to the server
*/
submitFile(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('file', this.file);
/*
Make the request to the POST /single-file URL
*/
axios.post( '/file-preview',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
/*
Handles a change on the file upload
*/
handleFileUpload(){
/*
Set the local file variable to what the user has selected.
*/
this.file = this.$refs.file.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.file ){
/*
Ensure the file is an image file.
*/
if ( /\.(jpe?g|png|gif)$/i.test( this.file.name ) ) {
/*
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.file );
}
}
}
}
}
</script>
Next up, we will be previewing multiple files! The concepts remain the same, but there’s a few more gotchas. Don’t worry, we will cover them all!
You can download this component on Github.
Multiple File Upload Previews
Multiple files always present a unique challenge. In this case, it’s displaying a preview for all of the files selected. We will be keeping the use cases for the multiple file preview component very minimal. Of course you can always add a way for users to remove the files before uploading like we did in the last tutorial: Uploading Files With VueJS and Axios – Server Side Up. We will just be allowing users to upload as many images as they want and display a preview for each image.
Once again, we will be limiting the uploads only to images for the sake of previewing the files before they have been submitted.
The first thing we will do is create a simple FileMultiplePreview.vue
file which will handle all of the file’s multiple preview.
Add the template and style for multiple file upload previews
The first thing we will do is add the template for our file previews:
<style>
input[type="file"]{
position: absolute;
top: -500px;
}
div.file-listing img{
max-width: 90%;
}
</style>
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Files
<input type="file" id="files" ref="files" accept="image/*" multiple v-on:change="handleFilesUpload()"/>
</label>
</div>
<div class="large-12 medium-12 small-12 cell">
<div class="grid-x">
<div v-for="(file, key) in files" class="large-4 medium-4 small-6 cell file-listing">
{{ file.name }}
<img class="preview" v-bind:ref="'image'+parseInt( key )"/>
</div>
</div>
</div>
<br>
<div class="large-12 medium-12 small-12 cell clear">
<button v-on:click="addFiles()">Add Files</button>
</div>
<br>
<div class="large-12 medium-12 small-12 cell">
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
First, I added a few styles to make the functionality work. I hid our file input off the page and then when Add Files
is clicked, we fire a click on the off page element to bring up the file selection. We do this so we can have a cleaner interface to work with. The other style is just setting the max width of the image so there isn’t massive overflows if the user uploads a large image.
If we look at the file input in the template you will see a few new attributes:
<label>Files
<input type="file" id="files" ref="files" accept="image/*" multiple v-on:change="handleFilesUpload()"/>
</label>
First, and most minor is we adjusted the ref
attribute to be files
instead of file
. The most important attribute, is the multiple
attribute which allows users to select multiple files. We also kept the accept="image/*"
attribute which allows the user to only upload images. Finally, we handle the uploads with the v-on:change="handleFilesUpload()"
method. This listens to when the user has uploaded a file, we handle the preview and storing the data.
Next, you will see a VueJS iterator that iterates over the uploaded files and displays a preview:
<div v-for="(file, key) in files" class="large-4 medium-4 small-6 cell file-listing">
{{ file.name }}
<img class="preview" v-bind:ref="'image'+parseInt( key )"/></div>
What this does is displays the name of the file and sets up an image preview. In the image tag, we have an attribute for reference that is dynamic with the v-bind:
prefix. What we do here is bind a reference named image{key}
. We use the index of the image in the files array to generate a unique identifier that we can reference through the global $refs
object. You will see how we do this as we get to adding the functionality, but just know we will be filling in each image tag with the appropriate preview from the keyed reference.
Next up, we have the add files button:
<button v-on:click="addFiles()">Add Files</button>
This allows the user to add initial files and any more after their initial selection. Each time the user adds files, they will be pushed on the local files array which will trigger the reactive rendering through VueJS and display a preview.
Finally, we have the submit button which simply sends the files to the server:
<button v-on:click="submitFiles()">Submit</button>
We will be going through all of these methods and explaining how they work in the next steps.
Add Data to Script Section of Component
Before we add any functionality, we should add the data variables for our component. On the top of the <script>
section, add the following code:
/*
Defines the data used by the component
*/
data(){
return {
files: []
}
},
All that we need for data is to initialize an empty files array. We will add all files to this as we go.
Since the image previews are rendered when there is data, we don’t need a show hide method. We also don’t need a placeholder, we will be filling in the <img>
tag src through the references which we will see in the next steps.
Handle Multiple File Uploads With Previews
So when the user uploads files, and the file input changes, we listen to the change and handle it with the handleFilesUpload()
method in our component’s methods object:
/*
Handles the uploading of files
*/
handleFilesUpload(){
/*
Get the uploaded files from the input.
*/
let uploadedFiles = this.$refs.files.files;
/*
Adds the uploaded file to the files array
*/
for( var i = 0; i < uploadedFiles.length; i++ ){
this.files.push( uploadedFiles[i] );
}
/*
Generate image previews for the uploaded files
*/
this.getImagePreviews();
},
First, we grab all of the uploaded files from the input and set it to a local variable.
We then iterate over all of the uploaded files and add them to the local files variable so we can iterate over it in our template.
Finally, we call the getImagePreviews()
method. This will display the image preview for each image selected. We build this next!
Build getImagePreviews() method
This method is the core of displaying the image previews for the uploaded files. In our Vue component’s methods we should add the following method:
/*
Gets the preview image for the file.
*/
getImagePreviews(){
/*
Iterate over all of the files and generate an image preview for each one.
*/
for( let i = 0; i < this.files.length; i++ ){
/*
Ensure the file is an image file
*/
if ( /\.(jpe?g|png|gif)$/i.test( this.files[i].name ) ) {
/*
Create a new FileReader object
*/
let reader = new FileReader();
/*
Add an event listener for when the file has been loaded
to update the src on the file preview.
*/
reader.addEventListener("load", function(){
this.$refs['image'+parseInt( i )][0].src = reader.result;
}.bind(this), false);
/*
Read the data for the file in through the reader. When it has
been loaded, we listen to the event propagated and set the image
src to what was loaded from the reader.
*/
reader.readAsDataURL( this.files[i] );
}
}
}
Alright, let’s step through the functionality.
First, we iterate over all of the files the user has uploaded. These were copied to the local files
array:
for( let i = 0; i < this.files.length; i++ ){
Next, we check to see if the file is an image. I’m sure there are other ways to preview PDFs and other file types if supported, but for the sake of this article, we will be previewing images. We need to make sure the uploaded file is an image by checking the extension:
if ( /\.(jpe?g|png|gif)$/i.test( this.files[i].name ) ) {
Now, we create a new FileReader()
object, and listen to the load
event. We bind the values of the local component through the .bind(this)
attached to the handler function.
reader.addEventListener("load", function(){
this.$refs['image'+parseInt( i )][0].src = reader.result;
}.bind(this), false);
Inside this method is where we reference our global $refs
object with the keyed reference we generated in our template. When we iterate over the images, the indexes match up and we can display the preview for each image.
Finally, we call the method to read in the file:
reader.readAsDataURL( this.files[i] );
This will read in the file at the key in the array. When the file has been loaded, we will trigger the load
event and set the source of the image tag in the $refs
array for the index we are at.
Pretty slick eh? That’s about it, I’ll run through the rest of the methods real quick, but they are very similar to what has been discussed in the previous tutorial: Uploading Files With VueJS and Axios – Server Side Up. Now you can display preview images for multiple file uploads!
Build addFiles() method
This method occurs when the user clicks the Add Files
button. The method should look like this:
/*
Adds a file
*/
addFiles(){
this.$refs.files.click();
},
What this does fire a click on the files
input to prompt the user to upload files. This way we can hide the input off the screen and build a nice UI.
Build submitFiles() method
This method handles the submissions of the files to the server for processing. The method should look like:
/*
Submits files to the server
*/
submitFiles(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
/*
Make the request to the POST /select-files URL
*/
axios.post( '/file-multiple-preview',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
The first part of the method, we initialize the FormData()
which will hold all of the data we send to the server.
/*
Initialize the form data
*/
let formData = new FormData();
Next, we iterate over all of the files and add them to the formData
variable we just created.
*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
This builds a nice array of files we send to the server.
Finally, we send the files to the server:
/*
Make the request to the POST /select-files URL
*/
axios.post( '/file-multiple-preview',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
For this example I just created an endpoint on my API with the URL /file-multiple-preview
. Next, I passed the formData
to the axios.post()
method as the second parameter. If there were any other form fields, you could add them to the formData
variable. The third and final parameter is an object of configuration for the request. All I added here was the header to alert the server of possible files which was the 'Content-Type': 'multipart/form-data'
.
I also attached a method for success which prints to the console and caught any unsuccessful requests. These should be tweaked depending on your needs and implementation.
Our final FileMultiplePreview.vue
component should look like:
<style>
input[type="file"]{
position: absolute;
top: -500px;
}
div.file-listing img{
max-width: 90%;
}
</style>
<template>
<div class="container">
<div class="large-12 medium-12 small-12 cell">
<label>Files
<input type="file" id="files" ref="files" accept="image/*" multiple v-on:change="handleFilesUpload()"/>
</label>
</div>
<div class="large-12 medium-12 small-12 cell">
<div class="grid-x">
<div v-for="(file, key) in files" class="large-4 medium-4 small-6 cell file-listing">
{{ file.name }}
<img class="preview" v-bind:ref="'image'+parseInt( key )"/>
</div>
</div>
</div>
<br>
<div class="large-12 medium-12 small-12 cell clear">
<button v-on:click="addFiles()">Add Files</button>
</div>
<br>
<div class="large-12 medium-12 small-12 cell">
<button v-on:click="submitFiles()">Submit</button>
</div>
</div>
</template>
<script>
export default {
/*
Defines the data used by the component
*/
data(){
return {
files: []
}
},
/*
Defines the method used by the component
*/
methods: {
/*
Adds a file
*/
addFiles(){
this.$refs.files.click();
},
/*
Submits files to the server
*/
submitFiles(){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Iteate over any file sent over appending the files
to the form data.
*/
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('files[' + i + ']', file);
}
/*
Make the request to the POST /select-files URL
*/
axios.post( '/file-multiple-preview',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function(){
console.log('SUCCESS!!');
})
.catch(function(){
console.log('FAILURE!!');
});
},
/*
Handles the uploading of files
*/
handleFilesUpload(){
/*
Get the uploaded files from the input.
*/
let uploadedFiles = this.$refs.files.files;
/*
Adds the uploaded file to the files array
*/
for( var i = 0; i < uploadedFiles.length; i++ ){
this.files.push( uploadedFiles[i] );
}
/*
Generate image previews for the uploaded files
*/
this.getImagePreviews();
},
/*
Gets the preview image for the file.
*/
getImagePreviews(){
/*
Iterate over all of the files and generate an image preview for each one.
*/
for( let i = 0; i < this.files.length; i++ ){
/*
Ensure the file is an image file
*/
if ( /\.(jpe?g|png|gif)$/i.test( this.files[i].name ) ) {
/*
Create a new FileReader object
*/
let reader = new FileReader();
/*
Add an event listener for when the file has been loaded
to update the src on the file preview.
*/
reader.addEventListener("load", function(){
this.$refs['image'+parseInt( i )][0].src = reader.result;
}.bind(this), false);
/*
Read the data for the file in through the reader. When it has
been loaded, we listen to the event propagated and set the image
src to what was loaded from the reader.
*/
reader.readAsDataURL( this.files[i] );
}
}
}
}
}
</script>
Github Component
Feel free to download this component here.
If you have any questions, definitely let me know!
UPDATE 10/14/2021: Removal of $refs
You can remove the $refs
attribute by passing the event
as the only parameter to the handleFileUploads()
method. This will work for both components and is demoed in the Github repository.
Conclusion
This should add a few more bells and whistles when uploading a file with AJAX using VueJS and Axios. The image previews are super helpful when building a seamless user experience. Adding these types of user experiences to the front end of your application is also helpful when building an API Driven Application. I’m writing a book on how to build API Driven Applications, so if you want to be the first to know when it’s read, sign up here: Server Side Up General List.
I’m also working on a few more tutorials. One to add file upload progress bar and another to add drag and drop uploading with previews and progress indicators that works with multiple files and through AJAX with VueJS and Axios. This should be most of the features needed for a slick file upload process, so stay tuned!
If you have any questions or comments, please reach out or ask in the comment section below!