dojo + php = multi-file upload + progress bar

In a previous post (simple multifile upload with dojo), I explained how to have multifile upload thanks to dojo.

In this post we will simply add the progress bar to our uploader (=> you might find useful to read the previous post first).
For now, flash server file served with dojo for testing looks like it’s not working properly (or at least that I don’t use it properly) => for this post, I will focus on JS multi-file uploader (you will have to select files one by one, sorry).

server side requirements

By default, php does not permit to access file-upload progress information. We need to activate dedicated php module for this. In our case, we will use the APC (alternative php cache) module.

  1. install the module (sudo apt-get install php-apc)
  2. modify php.ini (sudo vi /etc/php5/apache2/php.ini) to add the following line: “apc.rfc1867 = 1” (without double quotes, everything in a dedicated single line)
    note: by default, even after installation of apc module, apc.rfc1867 option is turned off, so we need to activate it using php.ini otherwise we won’t be able to access file-upload progress information
  3. restart apache (sudo /etc/init.d/apache2 restart)

That’s it.

Our server should be up and running.
To ensure everything is ok, display a single php file with “<?php phpinfo();” as content and ensure apc.rfc1867 option is turned on as below:
apc.rfc1867

Javascript

Let’s have fun now.

The idea here is to capitalize on great work done by mike wilcox and its dojox.form.FileUploader widget, and to add our progress bar with a non-flash solution and to use dijit.ProgressBar as well to avoid the trouble of creating our own progress bar.

You can see a live example right here: progress multifile dojo uploader
note1: no design has been done, simply click on the text and choose the file to upload. Repeat the selection step to add another file for upload, then click on upload button. – seee at the end of the post for more details
note2: I did not display currently uploaded size, nor total size nor average speed. I could have done it, but as you will see at the end of the post, I am not entirely satisified with current implementation => I will do it on next implementation

Here is the js code:

dojo.require('dijit.ProgressBar');
dojo.require("dojox.form.FileUploader");

dojo.addOnLoad(function() {

/**** create progress bar ****/
var progressHandlerH = {
bar: new dijit.ProgressBar({}, 'progressBarH'),
_apcNode:null,
_lastXhr:null,
addApcNode: function() {
// summary:
//		add hidden node necessary by apc module for php-servers to be able to return upload-progress information
// note:
// 		only one node is necessary, even if multiple file upload
if (!this._apcNode || !this._apcNode.parentNode) {
this._apcNode = dojo.create('input', {type:'hidden', name:'APC_UPLOAD_PROGRESS', value:getUniqueId()}, uploaderH._formNode, 'first');
}
},
query4Progress: function() {
// summary:
//		query server for info upon upload progress
// note:
// 		- this._apcNode.parentNode is automatically cleared by dojox.form.FileUploader => will stop refresh upon onComplete()
//		- xhrGet.fired = -1 when query has not completed (=> avoid triggering a new xhr request while previous one is not finished)
if (uploaderH.fileList.length && this._apcNode.parentNode) {
if (!this._lastXhr || -1 != this._lastXhr.fired) {
this._lastXhr = dojo.xhrGet({
url: 'progressStatus.php',
content:{apc:this._apcNode.value},
handleAs:'json',
}).addCallback(dojo.hitch(this, function(data) {
if (100 != this.bar.progress && data.total) {
this.bar.update({progress: (data.current/data.total * 100)});
}
return data;
})).addErrback(function(data) {console.error(data);});
}
clearTimeout(this.TO);
this.TO = setTimeout(dojo.hitch(this, 'query4Progress'), 1000);
}
},
TO: null
},
getUniqueId = function() {
// summary:
//		return a unique random id based on microtime + random string (required by apc field)
var universe = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', id = new Date().getTime() + '_';
for (var i = 0; i<10; i++) {
id = id + universe.charAt(Math.max(0, Math.floor(Math.random() * universe.length) - 1));
}
return id;
};

/**** FOR JS/HTML UPLOADER ****/
var uploaderH = new dojox.form.FileUploader({
isDebug:true,
deferredUploading:true,
uploadUrl:'receiveFile.php',
uploadOnChange:false,
progressWidgetId:progressHandlerH.bar.id,
force:'html',
fileListId:'attachmentListH'
},
"addFilesH"
), uploadButton = dojo.byId('uploadFilesH');

dojo.connect(uploadButton, 'onclick', uploaderH, 'upload');
dojo.connect(uploadButton, 'onclick', progressHandlerH, 'query4Progress');
dojo.connect(uploaderH, 'onChange', progressHandlerH, 'addApcNode');

});

How does it work?

Basically we simply add an hidden field into the form generated by dojox.form.FileUploader dijit. This field has a specific name “APC_UPLOAD_PROGRESS” and a unique value. If we want to be able to access file-upload progress information on server side, this is required by php (it must be placed before <file> elements => we placed it first).

Then, we simply launched an ajax-request (dojo.getXhr()) that query at regular interval our server (progressStatus.php), asking for data upon current upload process.

Here is our server side file, it calls a single function (apc_fetch()) and return data into json format:

<?php
//phpinfo();

// THIS IS AN EXAMPLE
// you will obviously need to do more server side work than I am doing here to check and move your upload.

// JSON.php is available in dojo svn checkout
require(dirname(__FILE__). "/JSON.php");
$json = new Services_JSON();

$data = null;
if (isset($_GET['apc']) && ($apc_id = $_GET['apc'])) {
$data = apc_fetch('upload_'.$apc_id);
unset($data['temp_filename']);
}

// yeah, seems you have to wrap iframeIO stuff in textareas?
$stats = $json->encode($data);

echo $stats;

Limitations

This is great, but honestly I’m a bit disappointed. If you try it with multiple files, progress-bar is still acurate but it looks like php does not allow you to access the size of each file prior to having fully uploaded it.
The only information you have is the total size of all files to uploaded, but not this info for each individual file.

What’s the consequence? It’s that you cannot show a progress bar on a per-file basis. You can still display the name of the file currently uploaded (this is returned by php server script) but you won’t know what percentage of that file has been uploaded until it is finished. => you can find out this value for the ultimate file only.

This is why I will write another post where I take a slightly different approach. Multifile-upload will still be possible, but in the background each file to be uploaded will have it’s own <form> and a chain of uploads will be triggered.
(basically we will generate a dojox.form.FileUploader for each file to be uploaded). I will try to have a nicer css too.

I hope this info helped you run your own progress-bar file-uploader with dojo & php.
Any comment is welcomed (spams are not considered comments though :-))

sources

3 Comments: Trackback URL | Comments RSS

  1. Ed Says:

    Do you have a downloadable zip for all of the files? I see that you give the source for the progress bar php file, but it would be great to see the rest of them as well.
  2. remy Says:

    Hi Ed, I will try to post them soon but I'm running out of time right now. In the mean time, you might be interested in an improved version (still in beta) of a dojo uploader : http://remydamour.com/dojo-uploader/advanced-uploader.php Firebug may be helpuf to retrieve corresponding source code. I hope that helps.
  3. Valdelievre Says:

    Hi guys ! I developed a script to upload multiple files ( like gmail ) using dojo framework. Each files are uploaded in a separe form. So that we can have a status for each file. You can see a live demo here : http://developers.sirika.com/mfu/ Cheers

One Trackback

  1. July 1, 2009 : multi-file upload with dojo « Willing wheels

Post a Comment

Your email is never published nor shared. You're allow to say what you want...