Skip to main content

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.

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

Post a Comment

Popular posts from this blog

Adding beforeRender and afterRender functions to a Backbone View

I was working on a Backbone application that updated the DOM when a response was received from the server. In a Backbone View, the initialize method would perform some operations and then call the render method to update the view. This worked fine, however there was scenario where in I wanted to perform some tasks before and after rendering the view. This can be considered as firing an event before and after the function had completed its execution. I found a very simple way to do this with Underscore's wrap method.

De-obfuscating javascript code in Chrome Developer Tools

I had blogged about JavaScript debugging with Chrome Developer Tools  some time back, wherein I have explained how these developer tools can help in debugging javascript code. Today Google Chrome 12 was released and my Chrome browser was updated to this version. As with every release, there have been some improvements made on performance, usability etc,. One feature that stood out for me is the ability to De-obfuscate the javascript code. What is Minification? Minification is the process of removing unnecessary characters such as white spaces, comments, new lines from the source code. These otherwise would be added to make the code more readable. Minifying the source code helps in reducing the file size and thereby reducing the time taken to download the file. This is the reason why most of the popular javascript libraries such as jQuery are minified. A minified jQuery file is of 31 KB in size where as an uncompressed one is about 229 KB. Unfortunately, debugging minified javascript f

On GraphQL and building an application using React Apollo

When I visualize building an application, I would think of using React and Redux on the front-end which talks to a set of RESTful services built with Node and Hapi (or Express). However, over a period of time, I've realized that this approach does not scale well when you add new features to the front-end. For example, consider a page that displays user information along with courses that a user has enrolled in. At a later point, you decide to add a section that displays popular book titles that one can view and purchase. If every entity is considered as a microservice then to get data from three different microservices would require three http  requests to be sent by the front-end app. The performance of the app would degrade with the increase in the number of http requests. I read about GraphQL and knew that it is an ideal way of building an app and I need not look forward to anything else. The GraphQL layer can be viewed as a facade which sits on top of your RESTful services o