File Uploads have yet to really catch up with the times and with technology. Many reasons for this. Security being a major one. It's an open door between the outside world and a server. And we can't just let any old file through to wreak havoc, if havoc is what it's there to wreak. So today I will be going over how to implement a file drag and drop module on your sites.
It's a somewhat involved process, and for sake of momentum I will focus on just the new standards that will make this happen, and I will leave out any compatability code.
Overview
The following will walk you through adding the ever so popular drag and drop file upload feature that we see on many a site. This example will only work on an HTML 5 compliant browser. However, you can easily detect if the browser supports said feature by checking if any of the drag events are supported.
Create a drop zone
First things first, we'll want to set up a drop zone for our files. This will be a pre-determined area somewhere on your site in which users will be able to drag files to. You'll want to make sure that it is designed in a way that will immediately tell the user that it is an area meant for dragging content into. Here's an example of a drop zone that I use on some of my sites.
Drag your file right over here!
It's quick and to the point, and it gives users plenty of room to drag their files into.
Tip #1: Don't give users a tiny area to drop files into
Style the drop zone
Feel free to use the following classes to style your dropzone.
.drag-drop{
width: 100%;
/* height: 100px; */
background-color: #1caff6;
margin-top: 30px;
border-radius: 10px;
border: dashed 2px white;
color: white;
text-align: center;
padding-top: 40px;
padding-bottom: 40px;
font-size: 14pt;
transition: .5s background-color, .5s color;
height: 100px;
font-family:inherit;
}
.drag-drop.dragover{
background-color: #0e384c;
color: white;
}
.drag-drop.uploading{
background-color: #0e384c;
color: white;
}
The dragover and uploading classes will be dynamically added during the corresponding event handlers in the JavaScript down below.
Setup the form
We will use a single input element setting the type to file. To read more about the File API, you can check out the Mozilla Developer Network page. The important parts to remember for this post however are as follows. You'll want your input form to look something like the following:
<form id="drag_files" class="drag-drop" method="post" action="" enctype="multipart/form-data" novalidate>
<input type="file" name="files[]" id="files" multiple />
<div class="drag-files-label">
Drag your file right over here!
</div>
</form>
A few things to notice in the above markup. Firstly, notice that I set the enctype of the form to multipart/form-data. Secondly, notice that I set the type of the input to file. This is an HTML 5 API and will not work on older browsers. Lastly notice the multiple specifier that we added to our input. That is also an HTML 5 property that allows our input to accept multiple files.
The drag events
There are a few events that will need to be handled in order to have a smooth and seemless process for our drag and drop. And we can start the process, by disabling the default behavior from each.
var form = document.getElementById("drag_files");
var input = form.querySelector('input[type="file"]');
window.addEventListener('load', function(){
['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop'].forEach(function (event) {
form.addEventListener(event, function (e) {
// preventing the unwanted behaviours
e.preventDefault();
e.stopPropagation();
});
});
});
We'll want to let the user know when they arrive at the drop zone with their files. So for any event in which the mouse is dragging an item onto the zone, we'll want to add a 'dragover' class to our main form.
['dragover', 'dragenter'].forEach(function (event) {
form.addEventListener(event, function () {
form.classList.add('dragover');
});
});
['dragleave', 'dragend', 'drop'].forEach(function (event) {
form.addEventListener(event, function () {
form.classList.remove('dragover');
});
});
Next up, we'll want to handle the actual drop event.
var droppedFiles;
form.addEventListener('drop', function (e) {
droppedFiles = e.dataTransfer.files; // the files that were dropped
if (droppedFiles.length > 0)
{
form.querySelector(".drag-files-label").innerHTML = droppedFiles[0].name;
// optional - submit the form on drop
var event = document.createEvent('HTMLEvents');
event.initEvent('submit', true, false);
form.dispatchEvent(event);
}
});
The drop event provides us with the DataTransfer object, which further gives us the list of File objects that were dropped. For the sake of the example, I've only taken the name of the first file and added it to the dropzone. If you so wanted, you could submit the form on drop, which would begin the upload process as soon as your mouse was de-clicked.
Because we have all of our files stored in the droppedFiles object, we can easily push this data to the form. Whether you want to submit the form immediately, or later on in the process, you'll want to use the following method to do so.
form.addEventListener('submit', function (e) {
if (form.classList.contains('uploading'))
return false;
form.classList.add('uploading');
e.preventDefault();
// gathering the form data
var ajaxData = new FormData(form);
if (droppedFiles) {
Array.prototype.forEach.call(droppedFiles, function (file) {
ajaxData.append(input.getAttribute('name'), file);
});
}
For each file that is stored in our droppedFiles object, we'll want to add those to a new formData object, of which you can read more about here. Next up, we're just going to create a standard Ajax request and send our form data along with it.
// ajax request
var ajax = new XMLHttpRequest();
ajax.open(form.getAttribute('method'), form.getAttribute('action'), true);
ajax.onload = function () {
form.classList.remove('uploading');
if (ajax.status >= 200 && ajax.status < 400) {
// do what you will :)
}
else {
alert('Error. Please, contact the webmaster!');
}
};
ajax.send(ajaxData);
});
Feel free to customize the code anyway that you wish, and if you found it useful, feel free to send me a message as well. The full source is down below for your copy and paste pleasure.
View full source
<html>
<head>
<style>
.drag-drop{
width: 100%;
/* height: 100px; */
background-color: #1caff6;
margin-top: 30px;
border-radius: 10px;
border: dashed 2px white;
color: white;
text-align: center;
padding-top: 40px;
padding-bottom: 40px;
font-size: 14pt;
transition: .5s background-color, .5s color;
height: 100px;
font-family:inherit;
}
.drag-drop.dragover{
background-color: #0e384c;
color: white;
}
.drag-drop.uploading{
background-color: #0e384c;
color: white;
}
</style>
</head>
<body>
<h3>Drag and drop</h3>
<div id="edit_photo" class="edit-photo-modal modal">
<div class="modal-title">Upload your profile image</div>
<form id="drag_files" class="drag-drop" method="post" action="" enctype="multipart/form-data" novalidate>
<input type="file" name="files[]" id="files" multiple />
<div class="drag-files-label">
Drag your file right over here!
</div>
</form>
</div>
<script>
var droppedFiles;
var isAdvancedUpload = true;
(function (document, window, index) {
// auto upload image on file upload
// feature detection for drag&drop upload
isAdvancedUpload = function () {
var div = document.createElement('div');
return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window;
}();
if (isAdvancedUpload)
{
var form = document.getElementById("drag_files");
var input = form.querySelector('input[type="file"]');
// needed for ajax upload
var ajaxFlag = document.createElement('input');
ajaxFlag.setAttribute('type', 'hidden');
ajaxFlag.setAttribute('name', 'ajax');
ajaxFlag.setAttribute('value', 1);
form.appendChild(ajaxFlag);
['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop'].forEach(function (event) {
form.addEventListener(event, function (e) {
// preventing the unwanted behaviours
e.preventDefault();
e.stopPropagation();
});
});
['dragover', 'dragenter'].forEach(function (event) {
form.addEventListener(event, function () {
form.classList.add('dragover');
});
});
['dragleave', 'dragend', 'drop'].forEach(function (event) {
form.addEventListener(event, function () {
form.classList.remove('dragover');
});
});
form.addEventListener('drop', function (e) {
droppedFiles = e.dataTransfer.files; // the files that were dropped
if (droppedFiles.length > 0)
{
form.querySelector(".drag-files-label").innerHTML = droppedFiles[0].name;
var event = document.createEvent('HTMLEvents');
event.initEvent('submit', true, false);
form.dispatchEvent(event);
}
});
form.addEventListener('submit', function (e) {
if (form.classList.contains('uploading'))
return false;
form.classList.add('uploading');
//form.classList.remove('is-error');
if (isAdvancedUpload) // ajax file upload for modern browsers
{
e.preventDefault();
// gathering the form data
var ajaxData = new FormData(form);
if (droppedFiles) {
console.log(droppedFiles.length);
Array.prototype.forEach.call(droppedFiles, function (file) {
ajaxData.append(input.getAttribute('name'), file);
});
}
// ajax request
var ajax = new XMLHttpRequest();
ajax.open(form.getAttribute('method'), form.getAttribute('action'), true);
ajax.onload = function () {
console.log('ajax onload');
form.classList.remove('uploading');
if (ajax.status >= 200 && ajax.status < 400) {
console.log('win');
}
else {
console.log('whoops');
alert('Error. Please, contact the webmaster!');
}
};
ajax.send(ajaxData);
}
});
}
}(document, window, 0));
</script>
</body>
</html>