How I made a drag-and-drop file uploader with Vue3 and Firebase
Published: 2024-06-01 19:16:08

There is shared responsibility between the client and the server when handling features such as uploading files. The client takes care of transferring the file to the server and providing a way to access the file. The server takes care of validating the upload, storing the file, file permissions, and creating the API for the client to send the file.

I started out by building the component where users will be able to drop the files. After I was done with the HTML template I had to find a way to ‘pay attention’ to drag and drop actions made by the user. Luckily, there are Javascript event listeners for drag and drop events such as: drag, dragstart, dragend, dragover, dragenter, dragleave, drop. I added these event listeners to the div tag that acted as the ‘drop zone’, however, by default most browsers redirect the user away from the app when a file is dropped. To avoid this behaviour I used event modifiers, which allowed me to handle what happens when a file is dropped. The event modifiers I added were ‘prevent’ and ‘stop’. Once the drop event fired I created an upload function where I handled most of the logic. 

```Javascript
<div
   class="w-full px-10 py-20 rounded text-center cursor-pointer border border-dashed border-gray-400 text-gray-400 transition duration-500 hover:text-white"
   :class="{ 'bg-[#4EE4A2] border-[#4EE4A2] border-solid': is_dragover }"
   @drag.prevent.stop=""
   @dragstart.prevent.stop=""
   @dragend.prevent.stop="is_dragover = false"
   @dragover.prevent.stop="is_dragover = true"
   @dragenter.prevent.stop="is_dragover = true"
   @dragleave.prevent.stop="is_dragover = false"
   @drop.prevent.stop="upload($event)" 
>
```

The next step was to retrieve the file/s that the user uploaded. To do this, I needed access to the $event object. The event object refers to the argument added to the event handler function, and it contains information regarding the event that caused the given handler to be invoked. The event object contains a property called ‘dataTransfer’, which is an object that contains the information on the files transferred.

Once I had access to the uploaded files the next step was to transfer them to the Firebase storage. Since the event object provided me with the files in an object format (this was tricky as the object has numeric keys and it looked like an array), the first step I took was to convert them into an array so that I can loop through the array and send each file to Firebase. I converted the object into an array using the spread operator, and iterated through the array. For each file I added a validation to check whether the file’s mime type corresponds to what I was expecting to receive from the user. Validation can be performed both on the client and server side. For this particular project I was allowing only mp3 files to be sent to the server. 
```Javascript
upload($event) {
      this.is_dragover = false

      // convert obj to array
      const files = $event.dataTransfer ? [...$event.dataTransfer.files] : [...$event.target.files]

      files.forEach((file) => {
        if (file.type !== 'audio/mpeg') {
          return
        }
}
```

I initialised the Firebase storage and set up a reference to the main storage which basically represents the bucket url. Once I had reference to the main storage I also created a child reference to the folder holding the audio files just to keep everything clean. The last step was to actually upload the files to Firebase, and to achieve this I used a Firebase specific function called uploadBytesResumable(ref, data, metadata). I choose this function because it exposes progress updates, which helped me create progress bars for the uploads.

```Javascript
upload($event) {
      this.is_dragover = false

      // convert obj to array
      const files = $event.dataTransfer ? [...$event.dataTransfer.files] : [...$event.target.files]

      files.forEach((file) => {
        if (file.type !== 'audio/mpeg') {
          return
        }

        const storageReference = ref(storage) 
        const podcastRef = ref(storageReference, `/podcasts/${file.name}`)

        const uploadTask = uploadBytesResumable(podcastRef, file)
}
```

This is roughly the logic behind how I managed to upload files to Firebase that came from a drag and drop event. I later added to the project animated progress bars, handled the responses from Firebase accordingly and stored the file data in Firebase database with additional fields such as who uploaded the audio file, the name of the file, genre, etc. Follow this link to see the full component logic: https://github.com/PentekTimi/podcast-listener/blob/master/src/components/AppUpload.vue