18
votes

I have a web site with a regular <input type="file"> file upload, POSTing the data to the backend when the form is submitted.

I would like to progressively enhance the form so that you can drop a file from outside the browser anywhere in the viewport (not just on the file input field, as built into some browsers) to upload it.

Whether or not the form autosubmits isn't important. So if the drag-and-drop only selects the file in the file field, without starting an upload, that's fine. I don't need support for multiple files. I don't need to show upload progress, thumbnails or anything fancy.

I know there are JS libs that support drag-and-drop uploads, but they all seem to upload via AJAX. I could do that, but then I would need to modify the backend and frontend to handle upload errors, redirect and show the right messages on success and so on.

I want a progressive enhancement that doesn't require any backend changes. It should happen synchronously using the form in the page. JS is fine, as long as the upload happens "in the foreground". Synchronous AJAX would not work, of course.

3
In Chrome, at least, you can set the files of an file input on drop: jsfiddle.net/qMmPr. Is that what you're after?pimvdb
@pimvdb That's beautiful! Exactly what I wanted. And I forgot to mention that as this is for an internal service, Chrome-only is fine. If you write that as an answer (in addition to the comment), I'll mark it as accepted.Henrik N

3 Answers

23
votes

Although not really "synchronous" (JavaScript execution won't actually halt), you can set the files selected by <input type="file"> programatically. In fact, such elements and dragging share their file backend implementation (File and FileList instances), so it's really straight-forward. What's more, due to both frontends using FileLists, dragging multiple files work just as seamlessly.

This works in Chrome (using jQuery): http://jsfiddle.net/qMmPr/.

$(document).on("dragover drop", function(e) {
    e.preventDefault();  // allow dropping and don't navigate to file on drop
}).on("drop", function(e) {
    $("input[type='file']")
        .prop("files", e.originalEvent.dataTransfer.files)  // put files into element
        .closest("form")
          .submit();  // autosubmit as well
});
0
votes

Thanks to @pimvdb comment, I came up with a pretty elegant solution.

Since drag and dropping on the <input type="file" /> works, why not making it full-screen on dragstart to make sure the user can't miss it? Anyway he is dragging so his intentions are clear at this moment.

Here's a demo: https://jsfiddle.net/08wbo4um

NB: unfortunately this doesn't seem to work in an iframe, but it does work on an actual page. You can still apprehend the behavior.

Here's the snippet:

  $('input[type="file"]').on('change', function(e){
    var fileName = e.target.files[0].name;
    if (fileName) {
      $(e.target).parent().attr('data-message', fileName);
    }
  });
  
  $(document).on('drag dragstart dragend dragover dragenter dragleave drop', function(e) {
    if ($('input[type="file"]').length) {
      if (['dragover', 'dragenter'].indexOf(e.type) > -1) {
        if (window.dragTimeout)
          clearTimeout(window.dragTimeout);
        $('body').addClass('dragged');
      } else if (['dragleave', 'drop'].indexOf(e.type) > -1) {
        // Without the timeout, some dragleave events are triggered
        // when the :after appears, making it blink...
        window.dragTimeout = setTimeout(function() {
          $('body').removeClass('dragged');
        }, 100);
      }
    }
  });
h3, p {
  text-align: center;
}

.form-group {
  margin: 30px;
}

.file-upload .form-control {
  height: 150px;
  outline: 1px dashed #ccc;
  outline-offset: -15px;
  background-color: #eee;
}
.file-upload .form-control:before {
  content: "\f093";
  font: normal normal normal 14px/1 FontAwesome;
  font-size: 3em;
  left: 0;
  right: 0;
  display: block;
  margin: 20px auto;
  text-align: center;
}
.file-upload .form-control:after {
  content: attr(data-message);
  left: 0;
  right: 0;
  bottom: 0;
  text-align: center;
  display: block;
}
.file-upload .form-control input[type="file"] {
  cursor: pointer;
  opacity: 0;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
}
body.dragged .file-upload .form-control input[type="file"] {
  /* Make sure it is full screen, whatever the position absolute container */
  position: fixed;
  top: -50vh;
  bottom: -50vh;
  left: -50vw;
  right: -50vw;
  height: 200vh;
  width: 200vw;
  z-index: 10002;
}

body:after {
  content: 'You can drop the file. :-)';
  font-size: 2em;
  text-align: center;
  line-height: 100vh;
  position: absolute;
  top: 10px;
  bottom: 10px;
  left: 10px;
  right: 10px;
  background-color: #eee;
  z-index: 10000;
  border-radius: 4px;
  border: thin solid #ccc;
  visibility: hidden;
  opacity: 0;
  transition: visibility 0s, opacity 0.5s ease;
}

body.dragged:after {
  opacity: 1;
  visibility: visible;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

<h3>Drag N Drop file upload without AJAX Demo</h3>
<p>Try drag and dropping a file. :-)</p>

<div class="form-group file-upload" required="required">
    <label class="cols-sm-2 control-label" for="document_file">File Upload</label><br>
    <div class="cols-sm-10">
      <div class="input-group">
        <span class="input-group-addon"><i class="fa fa-file" aria-hidden="true"></i></span>
        <div class="form-control" data-message="Click to select file or drag n drop it here">
          <input required="required" title="Click to select file or drag n drop it here" type="file" name="document[file]" id="document_file">
        </div>
      </div>
    </div>
  </div>
-1
votes

It can be done by turning autoUpload to false, collecting the files in an array, then on form submit do a single ajax call with all the files together with the form data, as described here.