DEV Community

loading...

Yet Another Vue Drag and Drop

hundredrab profile image 100 ・3 min read

This post was first published here.

I had spent an awfully long time figuring out how to create a drag-and-drop file input in our Vue app. Most of the articles handled drag-and-drop, but not a standard file picker. I wanted both, kind of like Dropzone.

I’ve linked the complete code at the bottom.

This post won’t cover the part where the file(s) are uploaded to a server, or the subsequent handling of that; too many people have written about that.

First let’s set up the app.

<main>
  <div id='app'>
    <div class="container">
      Add your files here:
      <br>
      <input type="file" name="file-input" multiple="True">
    </div>
  </div>
</main>

We already have a file input button; we’ll store the file in a variable called files.

var app = new Vue({
  el: '#app',
  data: {
    files: [],
  },
})

Prevent Default Behaviour

First things first, what I don’t want is the user to drag over a file and have that file open in the browser (the default behaviour). To prevent this, we can add a directive in the <div id="app"> itself. Thanks for the tip, Raymond Camden.

I add a directive to prevent the default behaviour of dragover and drop events in the entire app. However, I only want the user to be able to drop the files in the container area around the file input. Once this happens, the handleFileDrop method is called. I’ve also added an on-change directive to handle file uploads by the input button.

I’ve also added a list of all the files stored to get an idea of what’s going on. We’ll modify this list later.

<div id="app" @dragover.prevent @drop.prevent>
  <div class="container" @drop="handleFileDrop">
    Add your files here:
    <br>
    <input type="file" name="file-input"
        multiple="True" @change="handleFileInput">
    <ul>
      <li v-for="file in files">
        {{ file.name }} ({{ file.size }} b) 
     </li>
    </ul>
  </div>
</div>

Handle File Inputs

Let’s code up these two methods we’ve defined. (Most of this has been modified from this article.)

var app = new Vue({
  el: '#app',
  data: {
    files: [],
  },
  methods: {
    handleFileDrop(e) {
      let droppedFiles = e.dataTransfer.files;
      if(!droppedFiles) return;
      ([...droppedFiles]).forEach(f => {
        this.files.push(f);
      });
    },
    handleFileInput(e) {
      let files = e.target.files
        if(!files) return;
        ([...files]).forEach(f => {  
          this.files.push(f);
        });
    },
  }
})

This handles the file inputs both by the conventional method; and by the cooler drag-and-drop way.

Finishing touches

Notice something odd yet? We’ve got an out-of-place file input. Dropping files into the container does not affect this input, which still shows No files selected.

To remedy this, we wrap the input file with a div and style it, as explained in this kind of hack-y but ingenious answer.
Add your files here:

      <br>
      <div class="file-wrapper">
        <input type="file" name="file-input"
            multiple="True" @change="handleFileInput">
        Click or drag to insert.
      </div>
.file-wrapper {
    text-align: center;
    width: 200px;
    height: 3em;
    vertical-align: middle;
    display: table-cell;
    position: relative;
    overflow: hidden;
    background: gray; /* and other things to make it pretty */
}
.file-wrapper input {
    position: absolute;
    top: 0;
    right: 0;
    cursor: pointer;
    opacity: 0.0;
    filter: alpha(opacity=0);
    font-size: 300px;
    height: 200px;
}

Removing files

This is where we modify our list. So far, we’ve allowed only addition of files. For removal, we use the index of the file in the array and pass it on to a removeFile method, which just pops that element from the array.

<ul>
  <li v-for="(file, index) in files">
    {{ file.name }} ({{ file.size }} b)
    <button @click="removeFile(index)"
        title="Remove">X</button>
  </li>
</ul>
  methods:{
    removeFile(fileKey){
      this.files.splice(fileKey, 1)
    }
  // Other methods previously defined
}

That’s it. We have an interface for file upload which allows both click-and-select, and drag-and-drop inputs in a Vue application.

The complete code is available on Codepen, do check it out.

Discussion (0)

pic
Editor guide