Greg Pettit
Greg Pettit

Reputation: 10830

xhr2 file upload - accessing filenames with formData

I have an upload component to my application, which successfully uses the older form upload method in an iFrame. I have an Ajax poll that returns upload progress every 1000ms.

I am doing feature detection and allowing capable browsers to upload via xhr2. The theory is that I then have access to the progress on the client side; no need to poll, and the progress bar updates far more smoothly and elegantly! At the end of the batch upload, I need to redirect to a page showing a list of all the files uploaded.

You can upload files through xhr2 two ways: each file is its own upload, or you make a "formData" object containing all files. Each has benefits and drawbacks, and I think I'm trying to get the best of both worlds which may not be possible. The "best of both worlds" to me would meet these requirements:

  1. Be able to display progress and filenames of individual files as they go
  2. Be able to display a "total progress" bar as the package is sent
  3. Capture the "package" of files on the server side and send back a redirect upon completion of the whole package.

Here's sample code for single-file transfer. I can meet criteria #1 easily, and could make code for criteria #2 with some effort. #3 is a bit more problematic as the client side simply sends a bunch of separate files as individual transfers.

function uploadFile(file) {
      xhr = new XMLHttpRequest();

      xhr.upload.addEventListener("progress", function(evt) {
        if (evt.lengthComputable) {
          //domNode.bar is a cached jQuery object
          domNode.bar.css('width', (evt.loaded / evt.total) * 100 + "%");
          console.log('my current filename is: ' + file.name;
        }
        else {
          // No data to calculate on
        }
      }, false);

      xhr.addEventListener("load", function() {
        console.log("finished upload");
      }, false);

      xhr.open("post", remoteURL, true);

      // Set appropriate headers
      xhr.setRequestHeader("Content-Type", "application/octet-stream");
      xhr.setRequestHeader("X-File-Name", file.name);
      xhr.setRequestHeader("X-File-Size", file.size);
      xhr.setRequestHeader("X-File-Type", file.type);


      // Send the file
      xhr.send(file);
    }
}

Here's sample code for sending as formData. I cannot meet criteria #1 right now... at all. I can meet critera #2 and #3 fairly easily:

function uploadFile(form) {
      xhr = new XMLHttpRequest();

      xhr.upload.addEventListener("progress", function(evt) {
        if (evt.lengthComputable) {
          console.log(evt);
          domNode.bar.css('width', (evt.loaded / evt.total) * 100 + "%");
          // no longer have access to the file object
        }
        else {
          // No data to calculate on
        }
      }, false);

      xhr.addEventListener("load", function() {
        console.log(xhr);
      }, false);

      xhr.open("post", remoteURL, true);

      // Set appropriate headers
      xhr.setRequestHeader("Content-Type", "multipart/form-data");

      // Send the form
      xhr.send(form);
    }
}

// just assume that I've used formData.append to build a valid form object, 
// which I have; and that this is triggered by a click event or something
uploadFile(form);

I'm not completely without options, but before I move forward with implementation I just wanted to sanity check that I'm not missing anything. Here are the options as I see them:

  1. Continue to use only the Ajax calls for progress. Less smooth which is counter to our design goal, but the benefit is that I don't need to use an iFrame for the upload since I have the xhr2 API. I also get the single redirect when the stream closes.

  2. Iterate over the files and do some heavy lifting on the client side for the progress; in addition to individual file progress bars, I can create an algorithm to track total progress. At the same time, I send a file count to the server, and use this count to hold off on a redirect URL until the last file arrives, and then I can send it.

Any thoughts?

Upvotes: 0

Views: 1370

Answers (1)

Ray Nicholus
Ray Nicholus

Reputation: 19890

If you want to send all files in one request, FormData is your only option of the two. Either option allows you to cover points 1 & 2.

Since you asked about other APIs, I feel it is justified in mentioning the cross-browser upload tool I maintain: Fine Uploader. My library provides callbacks that will allow you to easily achieve goals #1 & #2. As far as #3 is concerned, Fine Uploader sends each file in a separate request. However, it is not hard to make use of Fine Uploader's API to present a "total progress" bar. I've done just that in a project that uses Fine Uploader. The library provides a bunch of callbacks and exposes a comprehensive API that should be useful to you, given the points you have specified.

Note that upload progress is not currently available in IE9 and older in Fine Uploader, but you should be able to make use of your method to display progress via ajax calls. Your method is one possible approach, but there is another possible option that is scheduled for further review in the future: use of Apache/nginx's UploadProgress module. See feature request #506.

Upvotes: 1

Related Questions