Utilizing the HTML5 File API to choose, upload, preview and see progress for multiple files
To me, something about HTML5 that makes it quite interesting is all the new support for file interaction. I’ve written about the File API and reading file information before, and I thought I’d expand on that and add uploads and progress bars.
Functionality
In my File API for uploading and reading file information for multiple files demo, as part of my Information and samples for HTML5 and related APIs demo web site, I’ve added two things:
- An
input
filed with thetype="file"
and having themultiple
attribute set to be able to choose more than one file at a time. - A drop area, i.e. element that you can drop files from your desktop/Finder/Explorer and upload them right away.
Please note that while files are uploaded in my demo, they are not actually saved on the server. It’s only to be able for you to see the file upload in Firebug/Web Inspector etc and also to present progress events.
Code needed
First, some simple HTML code:
<p> <input id="files-upload" type="file" multiple> </p> <p id="drop-area"> <span class="drop-instructions">or drag and drop files here</span> <span class="drop-over">Drop files here!</span> </p>
The span
elements are shown respectively hidden to give instructions and user feedback.
Adding change and drag events
Then we add events for detecting when the user has chosen any number of files for the input
element or have dropped files on the drop area:
filesUpload.addEventListener("change", function () { traverseFiles(this.files); }, false); dropArea.addEventListener("dragleave", function (evt) { var target = evt.target; if (target && target === dropArea) { this.className = ""; } evt.preventDefault(); evt.stopPropagation(); }, false); dropArea.addEventListener("dragenter", function (evt) { this.className = "over"; evt.preventDefault(); evt.stopPropagation(); }, false); dropArea.addEventListener("dragover", function (evt) { evt.preventDefault(); evt.stopPropagation(); }, false); dropArea.addEventListener("drop", function (evt) { traverseFiles(evt.dataTransfer.files); this.className = ""; evt.preventDefault(); evt.stopPropagation(); }, false);
Don’t worry about all the events for drag and drop, they just have to be there, ok? 🙂
Traversing files
We then have access to the files, either through a files
property on the input
element, or as a files
property on the evt.dataTransfer
property for drag and drop events:
function traverseFiles (files) { if (typeof files !== "undefined") { for (var i=0, l=files.length; i<l; i++) { uploadFile(files[i]); } } else { fileList.innerHTML = "No support for the File API in this web browser"; } }
Reading file information and previewing image files
For each chosen file we can then display three things for them:
- File name
- File size (in bytes)
- File type
// Present file info and append it to the list of files fileInfo = "Name: " + file.name + ""; fileInfo += "Size: " + parseInt(file.size / 1024, 10) + " kb"; fileInfo += "Type: " + file.type + ""; div.innerHTML = fileInfo;
An extra nifty feature, for any of the files that are an image, we can immediately read out the image data and display a preview of that image:
/* If the file is an image and the web browser supports FileReader, present a preview in the file list */ if (typeof FileReader !== "undefined" && (/image/i).test(file.type)) { img = document.createElement("img"); li.appendChild(img); reader = new FileReader(); reader.onload = (function (theImg) { return function (evt) { theImg.src = evt.target.result; }; }(img)); reader.readAsDataURL(file); }
Uploading files
What we use to upload the files is a regular XMLHttpRequest
object, and post the form appropriately:
// Uploading - for Firefox, Google Chrome and Safari xhr = new XMLHttpRequest(); xhr.open("post", "upload/upload.php", true); // Set appropriate headers xhr.setRequestHeader("Content-Type", "multipart/form-data"); xhr.setRequestHeader("X-File-Name", file.name); xhr.setRequestHeader("X-File-Size", file.size); xhr.setRequestHeader("X-File-Type", file.type); // Send the file (doh) xhr.send(file);
Progress and load events
To complement our file upload, it’s nice to show the upload progress for each file, and also detect when they have finished uploading. That is done by the help of the progress
and load
events:
// Update progress bar xhr.upload.addEventListener("progress", function (evt) { if (evt.lengthComputable) { progressBar.style.width = (evt.loaded / evt.total) * 100 + "%"; } else { // No data to calculate on } }, false); // File uploaded xhr.addEventListener("load", function () { progressBarContainer.className += " uploaded"; progressBar.innerHTML = "Uploaded!"; }, false);
The complete code
Here’s the complete code for the example:
<h3>Choose file(s)</h3> <p> <input id="files-upload" type="file" multiple> </p> <p id="drop-area"> <span class="drop-instructions">or drag and drop files here</span> <span class="drop-over">Drop files here!</span> </p> <ul id="file-list"> <li class="no-items">(no files uploaded yet)</li> </ul>
(function () { var filesUpload = document.getElementById("files-upload"), dropArea = document.getElementById("drop-area"), fileList = document.getElementById("file-list"); function uploadFile (file) { var li = document.createElement("li"), div = document.createElement("div"), img, progressBarContainer = document.createElement("div"), progressBar = document.createElement("div"), reader, xhr, fileInfo; li.appendChild(div); progressBarContainer.className = "progress-bar-container"; progressBar.className = "progress-bar"; progressBarContainer.appendChild(progressBar); li.appendChild(progressBarContainer); /* If the file is an image and the web browser supports FileReader, present a preview in the file list */ if (typeof FileReader !== "undefined" && (/image/i).test(file.type)) { img = document.createElement("img"); li.appendChild(img); reader = new FileReader(); reader.onload = (function (theImg) { return function (evt) { theImg.src = evt.target.result; }; }(img)); reader.readAsDataURL(file); } // Uploading - for Firefox, Google Chrome and Safari xhr = new XMLHttpRequest(); // Update progress bar xhr.upload.addEventListener("progress", function (evt) { if (evt.lengthComputable) { progressBar.style.width = (evt.loaded / evt.total) * 100 + "%"; } else { // No data to calculate on } }, false); // File uploaded xhr.addEventListener("load", function () { progressBarContainer.className += " uploaded"; progressBar.innerHTML = "Uploaded!"; }, false); xhr.open("post", "upload/upload.php", true); // Set appropriate headers xhr.setRequestHeader("Content-Type", "multipart/form-data"); xhr.setRequestHeader("X-File-Name", file.name); xhr.setRequestHeader("X-File-Size", file.size); xhr.setRequestHeader("X-File-Type", file.type); // Send the file (doh) xhr.send(file); // Present file info and append it to the list of files fileInfo = "Name: " + file.name + ""; fileInfo += "Size: " + parseInt(file.size / 1024, 10) + " kb"; fileInfo += "Type: " + file.type + ""; div.innerHTML = fileInfo; fileList.appendChild(li); } function traverseFiles (files) { if (typeof files !== "undefined") { for (var i=0, l=files.length; i<l; i++) { uploadFile(files[i]); } } else { fileList.innerHTML = "No support for the File API in this web browser"; } } filesUpload.addEventListener("change", function () { traverseFiles(this.files); }, false); dropArea.addEventListener("dragleave", function (evt) { var target = evt.target; if (target && target === dropArea) { this.className = ""; } evt.preventDefault(); evt.stopPropagation(); }, false); dropArea.addEventListener("dragenter", function (evt) { this.className = "over"; evt.preventDefault(); evt.stopPropagation(); }, false); dropArea.addEventListener("dragover", function (evt) { evt.preventDefault(); evt.stopPropagation(); }, false); dropArea.addEventListener("drop", function (evt) { traverseFiles(evt.dataTransfer.files); this.className = ""; evt.preventDefault(); evt.stopPropagation(); }, false); })();
Web browser support
Web browser support is pretty good for this – it’s working in:
- Firefox 3.5+
- Google Chrome 5.0+
- Safari 4.0+ (not complete support, and can be shaky/incorrect on Windows – but hey, who uses Safari on Windows anyway? 😉 )
- Opera 11.10+ (partial support)
- Internet Explorer 10+
For now, a reasonable fallback could be Plupload, which falls back to other technologies to try and do the trick.
Taking it to the next level
Here you have it. A nice, pretty simple script to read file information, preview thumbnails, upload files and offer the user feedback on the progress. Next steps would probably to be able to cancel uploads, testing out various fallback options for some web browsers etc. But hey, this is a good start, I think. 🙂
If you listen to the uploads event and use a cross-domain XHR, don’t forget you’ll have preflight requests.
Why not use
<progress>
?Thanks for sharing this, Robert. I am very excited that XHR now supports multipart/form-data and there will be a day where we won’t need to do the flash or iframe form posting tricks. Here is a list of browser support for File API. Safari has it planned for a near future release.
I would like to add that I have built a tiny wrapper for just the FileReader portion of this script, with support for multiple groups of file selections and some other features at https://github.com/bgrins/filereader.js. I didn’t realize how easy it was to post the data using XHR! I might have to add that into the plugin.
Paul,
Thanks for mentioning that!
Mathias,
Good question! I could definitely have used that.
Brian,
Glad you liked it!
Yeah, Safari is a bit uncertain on this topic…
Thanks for sharing the wrapper!
As per usual, great stuff Robert! Will be passing this article on to my students 🙂
Bramus!
Thank you! 🙂
Me, actually.
Martin,
I see!
What do you think of it?
No it’s not okay 🙂 Your contributions are pretty solid and a good nice read but please if you intend on explaining something to your readers do explain it throughly.
Could have taken you a paragraph or two at most. Or refer to some references/links for what ever you don’t feel like explaining for some reason…
Thanks.
Hady,
It’s actually a long explanation where a paragraph or two wouldn’t suffice, and since drag and drop isn’t vital to this example, I didn’t want to use half the post to write about the problems with the drag and drop behavior and specifications.
If you want to delve deeper into that, please read The HTML5 drag and drop disaster.
I’m sure I’m missing something, but my PHP file isn’t reading anything in the $_FILES global. (It’s an empty array).
Am I not capturing the data correctly?
[…] #HTML5 et upload de fichiers: #fdw #fichiers #upload : via LaFermeDuWeb […]
Chris,
Off the top off my head, I think you need to use $_SERVER[“CONTENT_LENGTH”] instead.
[…] permite s? faci upload prin drag&drop, afi?ând ?i progresul sau preview al imaginilor. Cite?te aici mai multe detalii despre cum s-ar putea face […]
I wrote a fix which modernizes older browsers and brings them up to speed with the File API spec using a silverlight plugin. Check it out http://sandbox.knarly.com/js/dropfiles/
I’d appreciate feedback
Andrew,
Interesting, thanks for the tip!
Really nice tutorial.
As you’ll probably discover from my question, I’m not a professional developer, it’s just a hobby. My understanding of this feature is that it doesn’t actually load the image but rather displays a kind of preview of it.
so is it correct to assume that it wouldn’t be possible to drag and drop any other type of file like text or video and also see it displayed in the browser?
Thanks, J
Jim,
Thanks! And no worries, all questions are welcome. 🙂
Well, it does load the image, but not the image file. What it does is read out the contents of the file and displays it as raw code in the web browser.
A video would be possible to show with the <video> element, and with a text file, it should, theoretically, be possible to get the contents of that file and show in an HTML element (not sure about this, though).
I was wondering if there is a way to allow the user to download the file from the file api?
Jared,
Well, not really. You will still need to have the file on your server, so just link directly to it. Otherwise, for the uploader, you can use things like readAsDataURL to get the content of the file and present it to the user.
Ive been trying to rack my head around this problem.
I can send the image file data and then get the file length through the SERVER[‘content_length’] method as you suggested. However I then need to process the image through a php class which fails.
Im wondering if it is because the class does not reckognize the file data as an image?
Is this the correct way to obtain the file? Ive tried request, Post, Files but cannot get anything to work. I dont usually like to ask for help but on this i am truly stuck and would greatly appreciate your advice.
John,
I believe you can get the content as data through
file_get_contents('php://input')
, and then just save it into a file.Hope it works out for you!
Ahh, what a star you are!
Still took a bit of head scratching but I got there in the end. I highly appreciate your help and this excellent tutorial.
I will post you a link when my site is up and running so you can check it out! (Could be some time yet though!).
Also ive found that you can put the files into your own array while traversing, so that the users can review / remove any photos before starting the upload process.
Thank-you, John.
John,
Good to hear!
Hi,
Very nice article!!
Now the question can this be made to work within an Adobe AIR application. I can’t get the drop to work properly!
Carl
Heres the AIR output I get when I try it within AIR:-
TypeError: Result of expression ‘files’ [null] is not an object.
traverseFiles at app:/html5-file-api-2.html : 208
at app:/html5-file-api-2.html : 243
Carl,
Thank you!
I’m sorry, I really don’t know. I guess it’s because of an older version of WebKit in Adobe AIR.
Thanks for the reply Robert!
>>I guess it’s because of an older version of WebKit in Adobe AIR.
Actually I don’t think they are that far apart:
This is the Adobe Air version:
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/531.9 (KHTML, like Gecko) AdobeAIR/2.5
This is the Safari version:
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-us) AppleWebKit/533.19.4 (KHTML, like Gecko) Safari/unknown
I’ll carry on working on it and lt you know for the record if I get anywhere with it.
Carl
Carl,
No, that’s not that big of a difference. Perhaps they have intentionally excluded it for now.
Good luck!
It is time to learn some more html5. I can see what you can do.
[…] the HTML5 File API to choose, upload, preview and see progress for multiple […]
The file APi seems to work width a number of files.
I have successfully uploaded png, gif, jpg, pdf, word.. and almost excel.
The very strange part is that uploaded excel-files can be sent back to the browser and perfectly readable in ‘Numbers’ on the mac while excel on windows reports an error. And it’s the formatting of the sheet that disappears when receiving the file in Chrome or Firefox/Win.
It’s not a filesystem-problem because Excel on Win can save the file directly to the share and reopen it. But when sending via the File Api it could not be open without error on Win.
Do You have any idea what might be the problem?
Thanks
/Kjell
Kjelle,
Interesting. Unfortunately I have no idea why that can be.
…and now I,ve discovered that the excel-file looses 0,6 bytes in the transformation….
[…] 1.Utilizing The HTML5 File API To Choose, Upload, Preview And See Progress For Multiple Files […]
The latest Opera snapshot supports the File API
http://my.opera.com/desktopteam/blog/2011/04/05/stability-gmail-socks
Paddy,
Yes, I saw that, and I’m looking forward to seeing it in a release!
Is it possible to read file data for specified number of bytes?
For example:
byte[] buffer = new byte[16 * 1024];
file.read(buffer,offset, length);
Is this possible in HTML5?
Thanks & regards,
Nadeem Ullah
Nadeem,
Sorry, no, not at this time that I am aware of at least.
I’m getting an error – “filesUpload is null” – pointing to the event listener:
filesUpload.addEventListener(“change”, function () {
I’ve tried selecting an image (or a different file type), but nothing initiates. I’ve copied the code exactly as above, what am I doing wrong?
RobM,
Please make sure you have an element in the page with the id files-upload. i.e. <input type=”file” id=”files-upload”>
Ok that part is now working – I moved the HTML form above the JS function.
But now I have another issue, for some reason my PHP upload script isn’t working. The call is made, and according to Firefox is loaded successfully….but none of the code within the file executes. I even threw an exception in the script to test it.
As you’ve probably guessed I’m not an advanced coder, still very much learning, so any help you can provide would be appreciated.
RobM,
It’s very hard to tell If you do it like this it should work.
Hi Robert
Thank you for providing this example. I had built my own implementation pretty far, but got some problems (with closures I assume…) to show individual progress bars (only last image had progress bar when dropped multiple images).
I have multiple drop areas in one page, so it got little bit complicated. Anyways, I got it all worked out with some help from your super clean example. That actually makes javascript look so simple for me too 🙂
apeisa,
Yes, closures and scope can always get to you sometimes. 🙂
Glad to hear that you learned something from it and that it was useful!
Opera 11.1 now supports the file API.
Mark,
Yep, I have it added to the compatibility tables for the test page.
I cant recognize the uploading file on php file
can anyone help what field and code shall i use to recognize such upload?
Mostafa,
You need to get the content as data.
I am using same code but it does not show the progress properly and some times it show but not exactly as it showing in crome .I am using mozilla firefox8.0 and tested with this demo also..same problem happen…
Vishal,
It should work fine, ot does that for me. Do you get any errors or similar?
[…] There are good tutorials about using the HTML5 File API to display preview like this or this. […]
Hi Robert
can you show me get file name and save it to database
i tried get file name from the Request.Headers[“X-File-Name”] parameter in code behind but have result null.
thanks u
Yoshikuni,
Please try the solution described above.
[…] I came accross this html multiple file upload tutorial: http://robertnyman.com/2010/12/16/utilizing-the-html5-file-api-to-choose-upload-preview-and-see-prog… […]
upload.php source would be nice to
maurice,
It’s actually empty. But within there, you could have the necessary code to read out the file data.
Hi Robert,
thank you for this tutorial.
Just want to say that file.fileName and file.fileSize did not work.
But file.name and file.size worked.
Thank you 🙂
kimsia,
Thanks, it was a typo – updated it!
[…] host OS file dialogs but it’s not particularly easy to determine host directory paths. This post outlines the demons web programmers must slay to select host files. JHS circumvents these […]
Dear Robert Nyman,
how can we upload this file(image) in to server . . .
Please help me!
– I am using Codeigniter Framework for my assignment –
Madushan,
It was described above.
tell me how to insert image url in database .. show me the php for it
jitu,
You need to refer to the PHP documentation and your database for how to do it. It’s described above how you read out the value of the posted file.
I need to file upload.php retrieve data
you help me
Actually this code send the file as HTTP input for the server.
Another solution is to send a Form embedding the file :
var formData = new FormData();
formData.append(“thefile”, file);
xhr.send(formData);
So you can use classic $_FILE[“thefile”] !
would this work for mobile?
Mr Nam,
It has been described above.
eBuildy,
Yep, I blogged about that in Using FormData to send forms with XHR as key/value pairs.
Dave,
If you have a mobile OS that supports selecting files it should work fine.
Can someone post the entire php code that is required for processing the image? on the server side applying validations for size and type etc and then storing it in a location??
Akhilesh,
For a more complete example, please see Using FormData To Send Forms With XHR As Key/Value Pairs
when i upload 5 file i want the uploader start only 1 file when complete and start to the next file. how can we do this ? and can we use stop or cancel upload function on this ?
You can do all of that, but then you need to tweak the script a lot.
I wonder how the server part works…you where you actually receive the file. Oh well irrelevant detail… 🙁
In case you are using angular.js, check out this simple/light-weight plugin that supports file upload for non HTML5 (IE8+) with flash polyfill, file drag and drop, progress bar and cancel upload in progress.
https://github.com/danialfarid/angular-file-upload
The code is not that angular.js specific so you can probably customize it for other frameworks with few changes.
[…] working with the HTML5′s File API (tutorial here) and I need to validate whether a file is an image or not. The main issue is that with a normal […]
i received this error
Warning: Missing boundary in multipart/form-data POST data in Unknown on line 0
is it working for excel file ?
It could work. You need to try it out.
how to show the preview before upload in internet explorer 9 and below versions..this code not working in internet explorer 9 and below versions..
I don’t think it was supported prior to IE9.
I appréciate this article. Its a good training for students. But i hope that the upload.php code will be added for more understanding.
many thanks.
An example was added above in the comments.
Its a good article thats provides multiple image uploads with multiple progress but hows we can Save images to folder on upload.php page, please suggest script for it
An example was added above in the comments.
Robert, Greetings from 2016! As a intern working on a project, how do you implement the HTML and JavaScript code to be used in a PHP file? Do I have to import the html and JS files into my php file? Or do I simply insert the code with the rest of it?
Hi,
As with any HTML, you have it directly in the PHP file, and as for the JavaScript code you either include it from that file or have it inline.