Upload πολλαπλών αρχείων από μία φόρμα

Αφορμή για αυτό το άρθρο στάθηκε αυτή η συζήτηση στο freestuff.gr.

Το ζητούμενο είναι να κατασκευαστεί μία σελίδα που θα διαχειρίζεται μία ΒΔ από events. Αν και αυτό από μόνο του δεν είναι και πολύ εξεζητημένο, η δυσκολία εμφανίζεται όταν θελήσουμε να επισυνάψουμε ένα σύνολο αρχείων σε κάθε event.

Για λόγους απλοποίησης, θα υποθέσουμε ότι κάθε event έχει ένα μοναδικό αναγνωριστικό (id) και ένα πεδίο περιγραφής (description). Κάθε επισυναπτόμενο αρχείο έχει κι αυτό ένα id, ένα πεδίο που δείχνει σε ποιο event ανήκει, το αρχικό filename και το path που αποθηκεύεται τοπικά στο server.  Επειδή δεν θέλω να κάνω οποιαδήποτε υπόθεση για τον τρόπο που θα επέλεγε ο καθένας για την αποθήκευση δεδομένων (RDBMS, filesystem, κάτι άλλο), στο παράδειγμά μας θα προσομοιώσουμε τη βάση στη μεταβλητή $_SESSION:

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]);
	}
	

Οι παραπάνω συναρτήσεις θα πρέπει να μετατραπούν ώστε να υλοποιούν τον τρόπο αποθήκευσης, κατά περίπτωση.

Αν θέλουμε να διαγράψουμε κάποιο event, θα το δηλώνουμε στο query string, δίνοντας delete={id}. Ομοίως, αν θέλουμε να γεμίσουμε τη φόρμα μας με μία εγγραφή για τροποποίηση, θα πρέπει να δώσουμε event={id}. Οι εισαγωγές και τροποποιήσεις θα γίνονται με post. Ο κώδικας που διαχειρίζεται αυτές τις λειτουργίες είναι παρακάτω:

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);

Η λειτουργία της πρώτης γραμμής είναι σαφής: Αν υπάρχει το $_GET[‘delete’], το αντίστοιχο event θα πρέπει να διαγραφεί. Επίσης, η τελευταία γραμμή “φορτώνει” τη μεταβλητή $cur_event με το event που προσδιορίσαμε ότι θέλουμε να επεξεργαστούμε. Αυτή με τη σειρά της, χρησιμοποιείται παρακάτω για να γεμίσουμε τη φόρμα μας.

Το ενδιαφέρον επικεντρώνεται στα πεδία της $_POST. Είναι προφανές ότι, αν υπάρχει $_POST[‘id’] τότε η λειτουργία είναι τροποποίηση, ενώ αν δεν υπάρχει έχουμε εισαγωγή. Αλλά το πιο σημαντικό είναι ο τρόπος που διαχειριζόμαστε τις εισαγωγές/διαγραφές των επισυναπτόμενων. Υπάρχουν δύο διαφορετικά πεδία, τα οποία μάλιστα είναι και arrays: το $_POST[‘delfile’] περιέχει τα ids των αρχείων που πρέπει να διαγραφούν και το $_FILES[‘newfile’] είναι ο πίνακας των νέων αρχείων που “ανέβηκαν”.

Τώρα, ας δούμε το κομμάτι της html. Η σελίδα θα χωρίζεται σε δύο τμήματα: έναν πίνακα απεικόνισης των events και μία φόρμα. Αν υπάρχει το $_GET[‘event’], η φόρμα γεμίζει με τα αντίστοιχα δεδομένα, αλλιώς περιμένει να δεχτεί μία νέα εγγραφή:

<!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>

Αυτό που μένει είναι η υλοποίηση των δύο συναρτήσεων delFile() και addFile() σε javascript. Η πρώτη θα διαγράφει την αντίστοιχη σειρά από τον πίνακα των επισυναπτόμενων και θα προσθέτει ένα νέο hidden πεδίο delfile[] με το id του αρχείου που πρέπει να διαγραφεί. Η δεύτερη, αντίστοιχα, θα εισάγει μία νέα σειρά και ένα νέο πεδίο newfile[] για να φιλοξενηθεί το νέο επισυναπτόμενο. Ο αντίστοιχος κώδικας είναι παρακάτω:

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();
	}

Κι εδώ τελειώνουμε. Τα σχόλια είναι πάντα ευπρόσδεκτα.

Καλές γιορτές!

2 Comments

Αφήστε μια απάντηση

Η ηλ. διεύθυνση σας δεν δημοσιεύεται. Τα υποχρεωτικά πεδία σημειώνονται με *