Dragging and dropping files from your desktop to a browser is one of the ultimate goals for web application integration. This is the first in a four-part series of posts which describes how to:
- enable file dragging and dropping onto a web page element
- analyze dropped files in JavaScript
- load and parse files on the client
- asynchronously upload files to the server using XMLHttpRequest2
- show a graphical progress bar while the upload occurs
- use progressive enhancement to ensure your file upload form works in any browser (good news for all you IE6 fans!)
- code it in plain ol’ JavaScript without a library.
Phew.
Big, Bad Browser Support
Before we begin, this tutorial refers to several cutting-edge HTML5 techniques so expect support to be patchy. The code works today, but it’s possible the APIs will change and browsers will evolve.
- Recent versions of Firefox and Chrome support all features and work perfectly.
- Opera can parse files in JavaScript, but file dropping and XMLHttpRequest2 uploading is not implemented.
- IE and the desktop editions of Safari do not support any of the APIs.
- Apple has disabled HTML file upload forms on the iPhone and iPad editions of Safari. Anyone know why?
Finally, note that my code shows the fundamental concepts. There’s little error checking and you would need to adapt it for a production system.
The HTML and CSS
Here’s our standard form with a file input type. The only HTML5 feature is the “multiple” attribute which allows the user to select any number of files.
We’ll be uploading files to a server running PHP but the code is much the same no matter what technology you’re using. The hidden MAX_FILE_SIZE value specifies 300,000 bytes — this is used by PHP but we’ll also check it client-side to prevent huge file uploads.
form id="upload" action="upload.php" method="POST" enctype="multipart/form-data"
fieldset
legendHTML File Upload/legend
input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="300000" /
div
label for="fileselect"Files to upload:/label
input type="file" id="fileselect" name="fileselect[]" multiple="multiple" /
div id="filedrag"or drop files here/div
/div
div id="submitbutton"
button type="submit"Upload Files/button
/div
/fieldset
/form
div id="messages"
pStatus Messages/p
/div
The #filedrag element will be used as our file drag and drop location. The element is hidden in CSS but it will be enabled in JavaScript if drag and drop is supported:
#filedrag
{
display: none;
font-weight: bold;
text-align: center;
padding: 1em 0;
margin: 1em 0;
color:
border: 2px dashed
border-radius: 7px;
cursor: default;
}
#filedrag.hover
{
color:
border-color:
border-style: solid;
box-shadow: inset 0 3px 4px
}
We’ve also defined a .hover class which changes the style when the user has dragged a file on to the element. Browsers don’t apply a :hover style in that situation, but we can add the class with JavaScript when the event occurs.
The File API
The W3C File API provides several objects. We’ll be using:
- FileList: represents an array of selected files.
- File: represents an individual file.
- FileReader: an interface which allows us to read file data on the client and use it within JavaScript.
Attaching JavaScript Events
Time to get our hands dirty with some JavaScript. We’re not using a JavaScript library so, to save our typing fingers, we’ll create a couple of helper functions to return an element by ID and output status messages:
// getElementById
function $id(id) {
return document.getElementById(id);
}
//
// output information
function Output(msg) {
var m = $id("messages");
m.innerHTML = msg + m.innerHTML;
}
We’ll now check if the File API is available and call an Init() function:
// call initialization file
if (window.File window.FileList window.FileReader) {
Init();
}
//
// initialize
function Init() {
var fileselect = $id("fileselect"),
filedrag = $id("filedrag"),
submitbutton = $id("submitbutton");
// file select
fileselect.addEventListener("change", FileSelectHandler, false);
// is XHR2 available?
var xhr = new XMLHttpRequest();
if (xhr.upload) {
// file drop
filedrag.addEventListener("dragover", FileDragHover, false);
filedrag.addEventListener("dragleave", FileDragHover, false);
filedrag.addEventListener("drop", FileSelectHandler, false);
filedrag.style.display = "block";
// remove submit button
submitbutton.style.display = "none";
}
}
The Init() function:
- Sets a “change” event listener to the file input element.
- Displays the #filedrag element.
- Sets “dragover” and “dragleave” event listeners to change the style of the #filedrag element.
- Sets a “drop” event listener for the #filedrag element.
- Hides the form submit button — it’s not required since we’ll be analyzing and uploading files as they’re chosen.
Optionally, you could hide the file input element when file dragging is supported. Personally, I prefer to offer both options since dragging and dropping incurs a number of usability issues.
The XMLHttpRequest.upload method check prevents problems in Opera. The browser supports File, FileList and FileReader, but not drag and drop events or XMLHttpRequest2. It can therefore display file information but we don’t want to show the #filedrag element or remove the submit button.
File Drop Style Change
Few people have experienced file drag and drop in a web browser. In fact, experienced web users may not consider it to be impossible. Therefore, we’ve used an element which states “drop files here”. We also want to indicate when a file has been dragged onto the #filedrag location by changing it’s styling:
// file drag hover
function FileDragHover(e) {
e.stopPropagation();
e.preventDefault();
e.target.className = (e.type == "dragover" ? "hover" : "");
}
Analyzing Dropped or Selected Files
We’re using the same FileSelectHandler() function regardless of whether one or more files was selected using “Browse” or dragged onto the #filedrag location:
// file selection
function FileSelectHandler(e) {
// cancel event and hover styling
FileDragHover(e);
// fetch FileList object
var files = e.target.files || e.dataTransfer.files;
// process all File objects
for (var i = 0, f; f = files[i]; i++) {
ParseFile(f);
}
}
The function:
- Calls FileDragHover() to remove hover styling and cancel browser events. This is essential otherwise the browser may attempt to display the file.
- Fetches a FileList object. This will either be from the file input box (e.target.files) or #filedrag element (e.dataTransfer.files).
- Finally, the function loops through all File objects in the FileList and passes it as an argument to the ParseFile() function…
function ParseFile(file) {
Output(
"pFile information: strong" + file.name +
"/strong type: strong" + file.type +
"/strong size: strong" + file.size +
"/strong bytes/p"
);
}
The function outputs information using the three main read-only properties provided by the File object:
- .name: the file name (it does not include path information)
- .type: the MIME type, e.g. image/jpeg, text/plain, etc.
- .size: the file size in bytes.
Please view the demonstration page in Firefox, Chrome, or Opera (no drag drop support). You can also download the files to examine the code.
We’ve covered a lot of ground. In my next article, we’ll discover How to Open Dropped Files Using HTML5 and JavaScript…