Mozilla

XHR progress and rich file upload feedback

This demo is by Olli Pettay (smaug) with help from Austin King.

A common limitation on the web today has been a rich file upload widget for web applications. Many sites use Flash or a desktop helper applications to improve the experience of uploading files.

Firefox 3.5 bridges one of these gaps allowing a better progress indicator to be built. Many developers don’t realize that they can use Firefox’s File object (nsIDOMFile) and XMLHttpRequest together to accomplish file uploads. This demo will feature an upload widget that gives the kind of rich progress feedback that users have come to expect, as well as fast and easy multiple simultaneous file uploads.

Progress Indicators

It’s always a good idea to expose feedback that your application is hard at work for them, and when the current action is expected to finish. The two main types of progress feedback are:

  • indeterminate progress – some activity just happened
  • deterministic progress I’m 40% done, I’m 42% done… etc

Deterministicsaidwhat? The Demo

We’ve created a simple file upload / download page that demonstrates the progress bar:

The demo is host at mozilla.pettay.fi and requires Firefox 3.5 beta4 or later. It demonstrates how to do multiple simultaneous file uploads without posting a form or leaving the current page. For each file upload / download we display the current speed, % complete, and bytes transmitted. We’ll go over a few key snippets of the code which are used in the screenshot above. Please click through the the demo and view source for the full code example.

The page contains two HTML inputs, one type="file" and one type="button". The form is never actually submitted, instead we add an onclick handler to the button:

<input type="file" id="file">
<input type="button"
          onclick="startXHR();"
          value="Upload file using XHR">

In the startXHR function, we create an XMLHttpRequest and add an event handler to the XHR request to listen for the new ‘progress’ event. With this ProgressEvent’s lengthComputable property, we will know if we are dealing with an indeterminate or deterministic progress. The object also gives us loaded and total bytes.

var xhr = new XMLHttpRequest();
 
xhr.onprogress = function(evt) {        
if (evt.lengthComputable) {
    evt.target.curLoad = evt.loaded;
    evt.target.log.parentNode.parentNode.previousSibling.textContent =
        Number(evt.loaded/k).toFixed() + "/"+ Number(evt.total/k).toFixed() + "kB";
}
if (evt.lengthComputable) {
    var loaded = (evt.loaded / evt.total);
    if (loaded < 1) {
        var newW = loaded * width;
        if (newW < 10) newW = 10;
            evt.target.log.style.width = newW + "px";
        }
    }
};

Now we need some data to send. We grab the contents of the file directly from the input by id:

var files = document.getElementById("file").files;
if (files) {
   var file = files.item(0);
   xhr.sendAsBinary(file.getAsBinary());

And the last step is to start the request:

xhr.open("POST", "cgi-bin/posthandler.pl");
xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
xhr.sendAsBinary(file.getAsBinary());

These methods would also work with xhr.upload.onprogress.

Notice the use of the sendAsBinary method on the XMLHttpRequest object and getAsBinary on the File object. Starting with Firefox 3 you’ve been able to get at the contents of a file on the client side without form submission. This is quite useful for moving beyond the limitation of tranditional file input and form submissions. It is also part of an up and coming W3C standard for FileUpload.

A related method that the nsIDOMFile provides is the getAsText method which returns a DOMString suitable for slicing, dicing, and displaying.

Here is an example usage, not used by the demo:

file.getAsText("utf8");

So that’s the gist of the code. Check out the demo and view it’s source.

Feedback In User Interfaces

Exposing system feedback to users improves perceived performance. We
can’t always determine how long something will take, so at a minimum we
can show indeterminate progress.

During file uploads and file downloads (assuming the server gives us Content-Length) we do indeed know the total number of bytes. Firefox 3.5’s Progress Events support adds a progress event so that we can show actual upload/download progress.

Traditionally XMLHttpRequests were difficult to get deterministic progress back from. In theory, you could give it callbacks and watch for status code updates and textual message updates, but in practice they turn out to be not very useful. In the past, if a deterministic progress meter was important, you’d have to make a second XHR request to poll for progress.

Enter Progress Events

The W3C has a working draft for Progress Events 1.0 which we include in Firefox 3.5. Firefox has added a key new DOM ProgressEvent progress event, as well as the loadstart event. The other existing events included: error, abort and load.

These same events are also available for uploads and downloads. The progress event gives us the following properties:

  • lengthComputable – true or false, is the size of the request known?
  • loaded – number of bytes received so far
  • total – number of bytes expected for entire request

The Contract

When you’re looking at the properties of those progress events, certain rules apply that you can depend on. They are:

  • The total property will be 0 when lengthComputable is false.
  • The loadstart event is always signaled only once.
  • The progress event is fired off zero or more times after loadstart.

And that’s it. Go forth and improve file uploads with Firefox 3.5 goodness.

38 comments

Comments are now closed.

  1. Dan wrote on June 18th, 2009 at 07:21:

    Awesome. Will the FileUpload standard enable this kind of thing for form submissions without the need for scripting?

  2. ozten wrote on June 18th, 2009 at 07:46:

    No, I don’t think so. The FileUpload spec standardizes the file choosing mechanism and API, so that it could be used in non-web browser environments. It’s still needs to be scripted.

  3. Matthew Wilson wrote on June 18th, 2009 at 09:34:

    What are the security restrictions here? You’ve made it sound as if web site Javascript can read the contents of any file on the local filesystem. I’m sure that isn’t the case, so can you outline what restrictions are in place?

  4. ozten wrote on June 18th, 2009 at 09:41:

    You cannot read a file until the user has chosen one with the file input, so it’s initiated and controlled by the user.

  5. xaron wrote on June 18th, 2009 at 09:58:

    Is there a multi-file-selecting support, too?
    Or have you to select each file one bye one?

  6. Ken Arnold wrote on June 18th, 2009 at 11:42:

    It looks like you can still only select one file at a time in the select box (using the Fx3.5 beta in Ubuntu Jaunty). But the API looks like it supports having a single file input select multiple files. Will this be supported in 3.5 final?

  7. scribu wrote on June 18th, 2009 at 13:56:

    That’s all very nice, but there’s still something missing: the ability to choose multiple files at once.

  8. Christopher Blizzard wrote on June 18th, 2009 at 18:53:

    @scribu – Yeah, we haven’t added multiple file uploads yet. I think we’re going to try to do that for our next release.

  9. @F1LT3R wrote on June 18th, 2009 at 19:19:

    Very cool! I have been wishing this was possible for about 5 years. Cool demo.

  10. Pingback from Ook interessant | Scriptorama.nl on June 18th, 2009 at 22:48:

    […] XHR Progress and Richer File Uploading Feedback – Scriptorama heeft er al eens eerder over geschreven: file upload progressbars. De mensen van Firefox geven nu nóg een methode. […]

  11. Pingback from links for 2009-06-19 | burningCat on June 19th, 2009 at 01:14:

    […] XHR progress and rich file upload feedback at hacks.mozilla.org A common limitation on the web today has been a rich file upload widget for web applications. Many sites use Flash or a desktop helper applications to improve the experience of uploading files. […]

  12. voracity wrote on June 19th, 2009 at 04:12:

    Wow, I never knew you could read file contents in 3.0. Awesome. I’m loving h.m.o.

  13. Gilberto Ramos wrote on June 19th, 2009 at 08:00:

    I should read more about XHR! This looks wonderful..

  14. Mariusz Nowak wrote on June 20th, 2009 at 07:14:

    I think much bigger limitation is lack of multi-file select and progress indicator is not that useful with just one file upload.
    So please please implement multi-file select.. Opera and Safari already has that ;-)

  15. Pingback from Firefox 3.5 on June 20th, 2009 at 14:34:

    […] Feedback mais rico nos uploads de ficheiros […]

  16. schrep wrote on June 21st, 2009 at 12:06:

    This is really cool – any idea on what other browsers do or will support this?

  17. Pingback from 颠覆网络35天 ─ 使用XHR制作文件上传组件 < MJiA on June 21st, 2009 at 23:36:

    […] 原文地址:XHR progress and rich file upload feedback 系列地址:颠覆网络35天 […]

  18. Christopher Blizzard wrote on June 22nd, 2009 at 09:11:

    @schrep – I’m not sure. I know that a lot of this stuff is showing up in the specs and the WK guys are pretty strong on this stuff so I’m sure that time frame will be short.

    IE is a different matter but they have picked up some new recent standards in recent versions. I’ll check with Arun to see if he has any feedback.

  19. Arun Ranganathan wrote on June 22nd, 2009 at 12:13:

    @schrep: synchronous stuff like *.getAsBinary() won’t have support from other vendors. I’m working on evolving an asynchronous File API (with callbacks). ProgressEvent support, however, will also be supported by other browsers.

  20. Pingback from File Uploads in Firefox 3.5 « From Accessibility to Zope on June 27th, 2009 at 06:26:

    […] improved developer control of browser-native file uploads, something I wishlisted back in 2007, is going to be available in the upcoming Firefox […]

  21. Pingback from Upgrade to Firefox 3.5 on Ubuntu 9.04 – Jaunty Jackalope | gaarai.com on July 1st, 2009 at 17:26:

    […] upload progress bars without the use of Flash or insane amounts of […]

  22. Mikko Ohtamaa wrote on July 6th, 2009 at 03:15:

    Could it be possible to resume uploads? This is an issue when trasferring large (video) files to the server?

  23. Neale wrote on July 13th, 2009 at 07:41:

    Great example, but I’m stuck on one thing. In the wonderful world of Firefox-based web applications, I’ve got a requirement where I am using XHR to POST a zip file which is then processed, resulting in a processed ZIP file being returned.

    What should be easy is for me to be able to get a handle on the async result, and to be able to either open that in a new window, or, as would happen in my case, get the download prompt.

    One option I have is to cache the resource on the server, and return a 201 + Location: for the cached zip, but that’s not ideal. I’d rather just stream the new zip out without hitting the disk.

    Any clues how I can do this?

  24. Pingback from 谋智社区 » Blog Archives » 颠覆网络35天 ─ 使用XHR制作文件上传组件 on August 3rd, 2009 at 21:18:

    […] 原文地址:XHR progress and rich file upload feedback 系列地址:颠覆网络35天 […]

  25. Pingback from Drag and drop file uploading using JavaScript | The CSS Ninja - All things CSS, Javascript & xhtml on August 29th, 2009 at 21:41:

    […] few people have already figured out and demonstrated multiple file uploading in safari 4 and a similar version that works in Firefox 3.5, although you can’t select multiple files you can upload more than […]

  26. John wrote on September 9th, 2009 at 13:14:

    How would someone post text field data along with the file upload to the same target page? Could you give us PHP code for the receiving page?

  27. wirtsi wrote on October 7th, 2009 at 23:04:

    I wrote a follow up to this post … including some JavaScript sample code on how to locally store all form data and submit it all at a later point

    http://blog.mykita.com/2009/10/enqueue-form-uploads-for-better-usability/

  28. Pingback from Anonymous on November 11th, 2009 at 07:46:

    […] […]

  29. Frankenst1 wrote on November 29th, 2009 at 09:39:

    Will not work with current firebug-1.4.5.xpi or firebug-1.5X.0b5.xpi since Firebug prevents XHR to fire onProgress-event.

  30. Shiv Kumar wrote on February 8th, 2010 at 11:31:

    How would you upload files along with form fields (multipart/form-data)?

    In what format are the file bytes actually sent to the server side?

    Are there any plans on supporting an onprogress event to the input element (type=”file”) such that one simply has to write code in the event delegate rather than everything else.

    At the moment it seems one can only upload files once. After that one has to close the browser and start again.

  31. Shiv Kumar wrote on September 21st, 2010 at 22:22:

    For anyone else that may have the same question as I did..

    you’d use
    fd.append(“uploadFile”, document.getElementById(‘fileField’).files[0]);

    where fielField happens to be the <input type=”file” id=”fileField” > element on the page.

    Files[0] will obviously give you the first file only.

  32. Markus wrote on October 25th, 2010 at 05:32:

    Great tutorial. I am knew to this, so can anybody tell how the server-side php code would look like to handle multi file upload. thx.

  33. Anil Namde wrote on January 20th, 2011 at 07:04:

    I am confused about the upload and download progress? By looking at the code
    initXHREventTarget(xhr.upload, container);
    initXHREventTarget(xhr, container);
    both are using the progress event. However the upload is progressing first and then the later.
    Can someone please explain me what and why is this difference in the behavior ?

    1. Shiv Kumar wrote on January 21st, 2011 at 03:54:

      Anil,

      What is the confusion exactly. There are two objects and each one has been assigned an event handler for each of their events. They happen to be assigned to the same methods.

      The demo page first uploads a file and then downloads a file. It’s not like it can upload and download the file at the same time. Nonetheless, the event handlers take care of displaying the visual progress.

      I have a simpler example on my site
      Html5 File Upload with Progress, you may want to check out.

  34. Anil Namde wrote on January 25th, 2011 at 07:34:

    Thanks, simple explanation that helped. Nice demo to start with the HTML 5 upload.

  35. Mike S wrote on July 28th, 2011 at 20:05:

    Can you recommend reading about how to show a progressbar and text message that shows how many thumbs have been downloaded completely out of the total number of thumbs for a slideshow page? e.g. Downloading file 13 of 83, with the progressbar showing (12/83*100) 14%?

  36. Stepan Suvorov wrote on July 30th, 2012 at 03:56:

    Number(evt.loaded/k).toFixed() + “/”+ Number(evt.total/k).toFixed() + “kB”;

    divided by “k” – but what is “k” variable here?

  37. Stepan Suvorov wrote on July 30th, 2012 at 04:55:

    and demo doesn’t work in my browser (FF14)

Comments are closed for this article.