Multiple file upload using one form
I was intrigued by this topic in freestuff.gr (in Greek) to write this article.
The question is to develop a web page that will handle an events database. Although this is pretty simple by itself, issues arise when we want to attach a set of files to each event.
For simplicity’s sake, we will assume that an event has a unique id and a description field. Each attachment consists of an id as well, the event it belongs to, the original filename and the path to the locally stored copy. I want to make no assumptions about the data storage policy (RDBMS, filesystem, something else) so in our example we will emulate the DB using the $_SESSION variable:
session_start();
if(!isset($_SESSION['events']))
$_SESSION['events']=array(
65745=>array(
'description'=>'An event',
'files'=>array(
4367=>array('filename'=>'image.png','path'=>uniqid()),
),
),
266432=>array(
'description'=>'Another event',
'files'=>array(),
),
245964=>array(
'description'=>'Yet another event',
'files'=>array(
6574=>array('filename'=>'document.doc','path'=>uniqid()),
6346=>array('filename'=>'diagram.png','path'=>uniqid()),
),
),
98656=>array(
'description'=>'An event too',
'files'=>array(
5423456=>array('filename'=>'photo001.png','path'=>uniqid()),
67547=>array('filename'=>'photo002.png','path'=>uniqid()),
28454=>array('filename'=>'photo003.png','path'=>uniqid()),
94376=>array('filename'=>'photo004.png','path'=>uniqid()),
45634=>array('filename'=>'photo005.png','path'=>uniqid()),
3246=>array('filename'=>'photo006.png','path'=>uniqid()),
),
),
);
function get_events_list()
{
$r=array();
foreach($_SESSION['events'] as $id=>$event)
$r[]=array(
'id'=>$id,
'description'=>$event['description'],
'filecount'=>count($event['files']),
);
return $r;
}
function get_event($eventid)
{
if(isset($_SESSION['events'][$eventid]))
return array(
'id'=>$eventid,
'description'=>$_SESSION['events'][$eventid]['description'],
'files'=>$_SESSION['events'][$eventid]['files'],
);
else
return array(
'id'=>null,
'description'=>'',
'files'=>array(),
);
}
function update_event($event)
{
$_SESSION['events'][$event['id']]['description']=$event['description'];
}
function insert_event($event)
{
$id=rand();
$_SESSION['events'][$id]=array(
'description'=>$event['description'],
'files'=>array(),
);
return $id;
}
function delete_event($eventid)
{
if(isset($_SESSION['events'][$eventid]))
unset($_SESSION['events'][$eventid]);
}
function insert_event_file($event,$filename,$path)
{
if(isset($_SESSION['events'][$event]))
$_SESSION['events'][$event]['files'][rand()]=array(
'filename'=>$filename,
'path'=>$path,
);
}
function delete_event_file($eventid,$fileid)
{
if(isset($_SESSION['events'][$eventid]['files'][$fileid]))
unset($_SESSION['events'][$eventid]['files'][$fileid]);
}
The above functions should be changed to implement the chosen storage policy.
If we want to delete an event, we will declare it in the query string, by giving delete={id}. Likewise, if we want to populate our form with a record for update, we will have to give event={id}. The inserts and the updates will be done using post. Folowing is the code that handles all these:
if(isset($_GET['delete'])) delete_event($_GET['delete']);
if(isset($_POST['id'])) // a form was posted
{
if($_POST['id'])
{
$id=$_POST['id'];
update_event($_POST);
if(isset($_POST['delfile']))
foreach($_POST['delfile'] as $file)
delete_event_file($id,$file);
}
else
$id=insert_event($_POST);
// insert uploaded files
if(isset($_FILES['newfile']))
for($i=0;$i<count($_FILES['newfile']['name']);$i++)
if($_FILES['newfile']['name'][$i])
insert_event_file(
$id,
$_FILES['newfile']['name'][$i],
$_FILES['newfile']['tmp_name'][$i]
);
}
$cur_event=get_event(isset($_GET['event'])?$_GET['event']:null);
The first line’s operation is clear enough: If there’s a $_GET[‘delete’], the corresponding event should be deleted. Moreover, the last line loads the $cur_event variable with the event we specified we wanted to update. This, in turn, is used later to populate the form.
What’s interesting here is the $_POST fields. Obviously, if there is a $_POST[‘id’] then an update is implied, otherwise we must insert a new record. But the most important part is the way we handle the attachments’ inserts and deletes. There are two different fields, which by the way are arrays: $_POST[‘delfile’] contains the ids of the files to be deleted while $_FILES[‘newfile’] is an array of the newly uploaded files.
Now, let’s take a look at the html part. The page will be divided in two parts: a table listing the events and a form. If there’s a $_GET[‘event’], the form is filled with the specified record, otherwise it will accept a new one:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Multifile Upload Form Demo</title>
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="multifile.js"></script>
</head>
<body>
<div id='dataset'>
<h2>Events</h2>
<table>
<tr><th>id</th><th>Description</th><th>Files</th><th>Actions</th></tr>
<?php foreach(get_events_list() as $row): ?>
<tr>
<td class='id'><?php echo $row['id']; ?></td>
<td class='description'>
<a href="?event=<?php echo $row['id']; ?>"><?php echo $row['description']; ?></a>
</td>
<td class='filecount'><?php echo $row['filecount']; ?></td>
<td class='delete'>
<a href="?delete=<?php echo $row['id']; ?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
<div id='eventform'>
<h2>
<?php if($cur_event['id']): ?>
Edit Event <a href='?'>(Add new instead)</a>
<? else: ?>
New Event
<?php endif; ?>
</h2>
<form method='post' enctype="multipart/form-data">
<input type='hidden' name='id' value="<?php echo $cur_event['id']; ?>" />
<label>
Description:
<input type='text' name='description' value="<?php echo $cur_event['description']?>" />
</label>
<fieldset>
<legend>Files:</legend>
<div>
<table id='filelines'>
<tr>
<th>id</th>
<th>Filename</th>
<th>Path</th>
<th>Actions</th>
</tr>
<?php foreach($cur_event['files'] as $id=>$file): ?>
<tr>
<td><?php echo $id; ?></td>
<td><?php echo $file['filename']; ?></td>
<td><?php echo $file['path']; ?></td>
<td><button type='button' onclick="delFile(this,<?php echo $id; ?>);">-</button></td>
</tr>
<?php endforeach; ?>
</table>
</div>
<button type='button' onclick='addFile();'>+</button>
</fieldset>
<input type='submit' value='Submit' />
</form>
</div>
</body>
</html>
The only missing part is the implementation of two javascript functions: delFile() and addFile(). The former will delete the corresponding row from the attachments’ table and add a new delfile[] field as a hidden input, with the id of the file to be deleted. The latter will add a new row, complete with a newfile[] field to accomodate the new attachment. See the relevant code below:
function addFile()
{
file=$('<label>File:<input type="file" name="newfile[]" /></label>');
delaction=$('<button type="button">-</button>');
delaction.click(function(){$(this).closest('tr').remove();});
$('#filelines').append(
$('<tr>')
.append($('<td>'))
.append($('<td>').append(file))
.append($('<td>'))
.append($('<td>').append(delaction))
);
}
function delFile(el,id)
{
$(el)
.closest('form')
.append("<input type='hidden' name='delfile[]' value='"+id+"' />");
$(el).closest('tr').remove();
}
That’s about it. Comments are always welcome.
Happy Holiday!
