Apr 27, 2011

File upload and Progress events with HTML5 XmlHttpRequest Level 2

The XmlHttpRequest Level 2 specification adds several enhancements to the XmlHttpRequest object. Last week I had blogged about cross-origin-requests and how it is different from Flash\Silverlight's approach.  With Level 2 specification one can upload the file to the server by passing the file object to the send method. In this post I'll try to explore uploading file using XmlHttpRequest 2 in conjunction with the progress events. I'll also provide a description on the new HTML5 tag - progress which can be updated while the file is being uploaded to the server. And of course, some ColdFusion code that will show how the file is accepted and stored on the server directory.


Progress Tag:

HTML5 has introduced several new elements to the language and one of them the progress element. This element is used to show a progress bar on the web page. This tag comes handy for describing any work in progress. It can be used to describe the progress of a time consuming task.The progress element comes with two attributes: value and max. The value attribute is used to indicate the current value of progress and the max attribute is used to describe completion value.

<progress id="progressBar" max="100" value="0"></progress>

XmlHttpRequest Progress Events:

The only event listener that was available with XmlHttpRequest was the readystatechange event listener. Now with the advent of HTML5, several event listeners have been added to the XmlHttpRequest Interface - loadstart, progress, load, abort, error, timeout and loadend. The loadstart event is triggered when the request is initiated by the client. The progress event is triggered once the loadstart event has been dispatched.

In this example, I'll try to upload a file (text, image, mp3) using XmlHttpRequest and show the progress for the same by updating the progressbar (progress tag) using the progress event handler. With Level 2 specification one can now pass ArrayBuffer, Blob, File and FormData objects to the send method.

The input tag with the type file can be used to select a file that needs to be uploaded to the server (<input id="fileUpload" type="file" />). Once the file is selected, a POST request can then be made to the server and the file object can be passed as an argument to the send method:

function uploadFile(){
    var filesToBeUploaded = document.getElementById("fileControl");
    var file = filesToBeUploaded.files[0];
    var xhrObj = new XMLHttpRequest();
    xhrObj.upload.addEventListener("loadstart", loadStartFunction, false);
    xhrObj.upload.addEventListener("progress", progressFunction, false);
    xhrObj.upload.addEventListener("load", transferCompleteFunction, false);
    xhrObj.open("POST", "upload.cfm", true);
    xhrObj.setRequestHeader("Content-type", file.type);
    xhrObj.setRequestHeader("X_FILE_NAME", file.name);
    xhrObj.send(file);
}

As observed in the above code, event listeners are added for loadstart, progress and load events. These events are added for the upload attribute of XmlHttpRequest object. The Content-type header is set to the value of file.type. This is required to let the server know the mime type of file being transferred. Also, the header X_FILE_NAME with the value of file.name is specified. The file object is then passed to the send method of the XmlHttpRequest object.

The loadstart event is triggered once a request to send a file to the server is initiated. Once this function has been dispatched the progress event is triggered indicating that the file is being uploaded to the server:

function progressFunction(evt){
    var progressBar = document.getElementById("progressBar");
    var percentageDiv = document.getElementById("percentageCalc");
    if (evt.lengthComputable) {
        progressBar.max = evt.total;
        progressBar.value = evt.loaded;
        percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";
    }
}

The progress event is triggered as and when some data is uploaded to the server. This depends on the size of file that is being transmitted. The progressFunction defined above, takes an argument of type ProgressEvent. The ProgressEvent Interface defines the attributes - lengthcomputable, loaded and total. The lengthcomputable attribute is used to find whether the progress is an indeterministic or deterministic one. In this case the size of the file being uploaded is known and hence the lengthcomputable attribute will be set to true. The other attributes loaded and total are used to indicate the number of bytes uploaded of the total number of bytes. This data is then used to update the progress tag attributes value and max.

Server side code:

The server-side code is as simple as it can get:

<cfset headerdata="getHTTPRequestData().headers">
<cfset content="getHTTPRequestData().content">
<cfset filePath = expandPath(".") & "/Uploaded/" & headerData.X_FILE_NAME >
<cfset content="content" filepath="filepath" filewrite="filewrite">

The header X_FILE_NAME would contain the name of the file that is being sent from the client. The file content is available in the request data and the same is accessed using getHTTPRequestData().content.

Update:

You can download the source code from - http://www.box.com/s/8py25tahymooe8pabo9z
Also, take a look at my post 'Uploading chunks of a large file using XHR2', which talks about how you can upload large files to a server that has a post parameter limit.

19 comments:

  1. Do you have a link for the demo? dowload?

    ReplyDelete
  2. I don't have a demo page. However, if you want the source code for the File upload, let me know your mail id. I'll send it to you.

    ReplyDelete
  3. This is my email address...please send the source code at the earliest...thanking u...

    ReplyDelete
  4. mahasish.shome@gmail.com
    This is my email address...
    please send the source code at the earliest...thanking u...

    ReplyDelete
  5. Thnx a lot for sending the source code...

    ReplyDelete
  6. Very interesting post. PLease could you send me the source code too. My email address is paul_edward at hotmail.co.uk

    Thanks

    Paul

    ReplyDelete
  7. This is my email vishalatole@gmail.com please send on this .
    Thanks

    ReplyDelete
  8. Doesn't work for binary data such as a .png file.

    ReplyDelete
  9. I was able to upload a png file. On which browser you are trying this example?

    ReplyDelete
  10. Hi, could you send me that source code please? email is: grorog ( at ) bluewin.ch

    ReplyDelete
  11. Hii Sagar..
    Hi, could you send me that source code please?
    vishal.hatiskar@gmail.com

    ReplyDelete
  12. I'm interested in checking out the source as well.
    davidv902@idvco.com
    Thanks

    ReplyDelete
  13. I've updated the post. You can download the source code from here

    ReplyDelete
  14. caokyniit@gmail.com
    can u send mail for me?

    ReplyDelete
  15. Sweet !!! Thank you and for the demo files too !!!

    ReplyDelete
  16. If you want to upload a large file in fragments using XHR2, see my post - http://www.sagarganatra.com/2012/08/uploading-chunks-of-large-file-using.html

    ReplyDelete
  17. Thanks for the code Sagar, I have a question.. this works fine on my CF9 but on CF10 I get progress bar 15% or whatever the first progress (depending on the size of the file) and then it aborts. I added event handler for error .. it aborts with an error. I see status 2 and nothing about error. Tomorrow, I will check CF logs but on top of your head.. any suspect(s)?

    ReplyDelete
  18. This might have to do with the post parameter limit. If you're uploading a large file you might get a 400 error. To address this, you need upload it in chunks. Refer to my post - 'Uploading chunks of a large file using XHR2' here - http://www.sagarganatra.com/2012/08/uploading-chunks-of-large-file-using.html

    ReplyDelete
  19. please send demo workmail1010@gmail.com ..... thanks

    ReplyDelete