28 Dec

multiple file uploads using HTML5

As a response to a reported bug where Chrome was taking ages to load up a flash multiple-file uploader, I’ve updated KFM to use HTML5’s multiple-file input box where possible.

To do this, first create the element:

  var input=document.createElement('input');
  input.type='file'; // use old-style JavaScript method to make sure all browsers respect it
  input.name='kfm_file';

Notice that we’re not using setAttribute to set the type and name – that’s a DOM method which works in most browsers but not (of course…) in Internet Explorer 6, where it has bugs.

And now, we tell the input to use the multiple-upload method. We use .setAttribute in this case because we only expect newer browsers to succeed with it.

  input.setAttribute('multiple','multiple');
  if(input.multiple)input.name='kfm_file[]';

In the second line, we check to see if the element is now marked as a multiple-uploader (most current browsers will not succeed in this), and if it does, then rename the input element by adding a [] to the end. If this is not done, then the server will only see the first file which is uploaded.

That’s the client-side done. This will only be visible in newer browsers such as Chrome, Safari 4, Firefox 3.6. I expect Internet Explorer will eventually catch up by 2020 or so.

If you’re doing this in pure HTML, then I suppose this would be good enough for you:

<input type="file" multiple="multiple" name="kfm_file[]"/>

In this case, you must put the [] in the name in all cases.

On the server-side, you need to write your upload receiver to expect either a single element, or an array.

For some really goddamned stupid reason, when multiple files are uploaded to PHP, the results are interlaced in a really crappy and awkward manner (I don’t like it).

Instead of something logical and easy to use, like this:

array(
  [0] => array(
    'name'     => 'file1.txt',
    'tmp_name' => '/tmp/abcdef'
    ....
  ),
  [1] => array(
    'name'     => 'file2.txt',
    'tmp_name' => '/tmp/ghijkl'
    ....
  )
);

You get this…

array(
  'name' => array(
    [0] => 'file1.txt',
    [1] => 'file2.txt'
  ),
  'tmp_name' => array(
    [0] => '/tmp/abcdef',
    [1] => '/tmp/ghijkl'
  ),
  ...
);

While that looks at first glance to be easy to use, it’s not. You can’t do a simple “foreach($_FILES['kfm_file'] as $file)” and expect the above to be usable at all…

So, the first thing I do, is to check for the $_FILES[‘kfm_file’], and convert it into the first form above, which is very easy to work with:

$files=array();
$fdata=$_FILES['kfm_file'];
if(is_array($fdata['name'])){
 for($i=0;$i<count($fdata['name']);++$i){
  $files[]=array(
   'name'     => $fdata['name'][$i],
   'tmp_name' => $fdata['tmp_name'][$i],
  );
 }
}
else $files[]=$fdata;

In my own case, I’m only interested in the name and tmp_name variables, so that’s all I set up.

Now you can do a foreach on $files and treat them all individually.

foreach ($files as $file) {
  // uploaded location of file is $file['tmp_name']
  // original filename of file is $file['name']
}

If you want to see this in KFM, have a look at the nightly-updated demo tomorrow, or download from SVN right now.

oh – and buy my book!