Wednesday, 13 February 2008

Upload progress bar by streaming JSON

Have you ever dealt with the problem of making an upload progress bar?
If you did, then it's time to continue reading.

I wanted to make one using javascript.
Basically, there are two alternatives:
  • The simpler one is to make periodic ajax requests to a url. That url must return the received_size/ total_size pair. All you need to do is to update your progress bar in the browser. This solution works in all those browsers which support AJAX. The disadvantage is that the browser will usually receive the (received_size/totoal_size) data pair delayed. So the progress bar won't show you the real progress.
  • The second option would be to use streaming. Instead of periodically polling the server, we should make one single request, and the server would stream the receive_size/total_pair data back to the browser.
I like this streaming alternative much more because it gives you a better UI experience.
Let me give you some hints on the streaming alternative.
  • The simplest and the secure way is to stream sequences of '<script type='text/javascript'>updateProgressBar(total, received)</script>' back to the browser. The returned javascript will be automatically executed in IE 6.0, Firefox, Safari and Opera. You can find a solution for this at Ry Dahl's article. However it works in a lot of browsers it's not the most elegant solution because you need to hardcode updateProgressBar in the server configuration file.
  • A little bit more elegant solution is to stream JSON expression like {"total_size": 10, "received_size": 0}; {"total_size": 10, "received_size": 1}; {"total_size": 10, "received_size": 2}; ...
If you are streaming JSON expressions you need to write a javascript function which updates the progress bar according to the last JSON slice. You can do this using this javascript utility. This script periodically checks the received content and calls your updateProgressBar function passing the last received JSON slice to it. Unfortunately this script doesn't works with the latest prototype library.

The shortest and the most elegant alternative is to use the "onInteractive" AJAX callback function. By using this callback we don't need to periodically check the response. It is called every time when new data arrives to the browser.
Look at this beautifull code:
new Ajax.Request('/upload/progress', {
pos :0,
onInteractive: function(obj) {
var slice = obj['responseText'].slice(this.pos)
var jsonFormat = slice.substring(0,slice.length - 2);
var json = jsonFormat.evalJSON();
updataProgressBar(json.totalSize, json.receivedSize);
this.pos = obj['responseText'].length + 1;

Unfortunately this callback doesn't work in Opera neither in IE.
And I didn't find any w3 recommendation for enforcing the browsers to implement it. :(

Let me summarize the opportunities and how are they supported on different browsers:

Opera latestFirefox 6IE 7
Periodic Ajax callsokokokokokok
Streaming <script> tagsokokokokokok
Streaming JSON, using Ry's javascriptokokokoknot workingnot working
Streaming JSON, using onInteractionnot workingokoknot workingnot workingnot working

So I need to keep up hacking till AJAX standard is born and implemented... :))