Friday, January 24, 2025

Responding to HTML5 FileReader Events

You may recall the recent Drag Files into the Browser from the Desktop with HTML5 article, which demonstrated how to use HTML5 Drag & Drop and HTML5 FileReader to accept image files from the Desktop and display them in the browser. The key to getting that to work was understanding the implications of the asynchronous nature of FileReader events. In today’s follow up article, we’re going to delve even deeper into the FileReader’s events by adding more detailed progress reports as well as some error handling.

Including Progress Information

My Determine an Image’s Type using the JavaScript FileReader article featured individual progress bars for each image load operation. Here too we can display progress information by listening to the FileReader’s onprogress event. This time, instead of a progress bar, I thought that it might be interesting to display a text message that includes the percentage completed and the file name. The interesting part is that accessing the file name requires binding the file.name property to the event handler, just as we bound the file object to the reader’s onloadend event in the “Drag Files into the Browser from the Desktop” article. Here’s the code for the onprogress event handler:

     

for (var i=0; i<files.length; i++) {

  var file = files[i];

  var reader = new FileReader();

 

  addEventHandler(reader, 'loadend', function(e, file) { 

    //function code

  }.bindToEventHandler(file));

      

  addEventHandler(reader, 'progress', function(e, fileName) { 

    if (e.lengthComputable) {

      var percentage = Math.round((e.loaded * 100) / e.total);

      status.innerHTML = 'Loaded : '+percentage+'%'+' of '+fileName;

    }                                           

  }.bindToEventHandler(file.name));

      

  reader.readAsDataURL(file);

}

Note that the file name is always the second argument because bindToEventHandler() passes the event object as the first argument

The New-and-improved bindToEventHandler() Method

Calling bindToEventHandler() for the onprogress event uncovered an issue with the existing code. While it worked perfectly well with one-time events such as the FileReader’s onloadend event, it did not process the progress event object correctly when called numerous times. The reason is that the closure caused the previous event state to be retained in the parameter array. Consequently, every call to the event handler added the latest event object to the start of the array. It’s actually a great way to retain an object’s history, but that’s not what we’re after here; the latest and greatest progress update is all we need. The solution is as simple as unshifting the progress event element to the boundParameters array outside of the closure and then updating the first boundParameters element within the returned function:

Function.prototype.bindToEventHandler = function bindToEventHandler() {

  var handler         = this;

  var boundParameters = Array.prototype.slice.call(arguments);

      boundParameters.unshift(''); //unshift requires an argument

 

  //create closure

  return function(e) {

      e = e || window.event; // get window.event if e argument missing (in IE)  

      boundParameters[0] = e; //update event info

 

      handler.apply(this, boundParameters);

  }

};

Here’s some sample progress event output in the Firefox log:

Loaded : 25% of Raw Sugar 2009.JPG

Loaded : 27% of Trapped in Conway!.JPG

Loaded : 27% of Trapped in Conway! 2.JPG

Loaded : 24% of Nien Nunb.bmp

Loaded : 51% of Raw Sugar 2009.JPG

Loaded : 53% of Trapped in Conway!.JPG

Loaded : 55% of Trapped in Conway! 2.JPG

Loaded : 48% of Nien Nunb.bmp

Loaded : 76% of Raw Sugar 2009.JPG

Loaded : 80% of Trapped in Conway!.JPG

Loaded : 82% of Trapped in Conway! 2.JPG

Loaded : 72% of Nien Nunb.bmp

Loaded : 96% of Nien Nunb.bmp

This is how a progress message appears in the browser:

onprogress

The above progress messages are replaced by a onloadend one similar to the following once the image has finished loading:

filereader_onloadend_message.jpg

Something Has Gone Horribly Wrong!

Well, not really, but it could! That’s why you need to attach some error handling to the FileReader’s onerror event. To make it easier to trap different kinds of errors, the error object of the onerror event contains a number of constants. They are:

  • NOT_FOUND_ERR (1): The file does not exist. A strange occurence considering that the user presumably selected or dragged the file from the file system. Nonetheless, it’s there if you need it, in the rare event that someone deletes the file between the time it is acquired and the time that the FileReader attempts to read it.
  • SECURITY_ERR (2): Security restrictions prevented the script from reading the file.
  • ABORT_ERR (3): The file read was aborted – typically when the user cancels.
  • NOT_READABLE_ERR (4): The file could not be read because of a change to permissions since the file was acquired – likely because the file was locked by another program.
  • ENCODING_ERR (5): The readAsDataURL() method failed because the file was too long to encode as a “data://” URL.

 

Here’s some code that listens to the onerror event and displays a message to the user to relate what went wrong. This time, we have to check the window.event property because we aren’t binding anything to this handler:

  addEventHandler(reader, 'progress', function(e, fileName) {

    //function code                                   

  }.bindToEventHandler(file.name));

      

  addEventHandler(reader, 'error', function(e) {

    e = e || window.event; // get window.event if e argument missing (in IE)  

    switch(e.target.error.code) {

      case e.target.error.NOT_FOUND_ERR:

        status.innerHTML = 'File not found!';

        break;

      case e.target.error.NOT_READABLE_ERR:

        status.innerHTML = 'File not readable!';

        break;

      case e.target.error.ABORT_ERR:

        status.innerHTML = 'Read operation was aborted!';

        break; 

      case e.target.error.SECURITY_ERR:

        status.innerHTML = 'File is in a locked state!';

        break;

      case e.target.error.ENCODING_ERR:

        status.innerHTML = 'The file is too long to encode in a "data://" URL.';

        break;

      default:

        status.innerHTML = 'Read error.';

    }       

  });

  

  reader.readAsDataURL(file);

}

A Word about Accessibility

Always keep in mind that, while drag & drop can make loading files from the desktop easier, there are users who can only use the keyboard. For that reason, drag & drop should always enhance the file input control and not replace it, so that users who can’t perform a mouse drag & drop operation can still complete the task.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured