Asynchronous multiple file upload με το swfupload
Μέχρι σήμερα έχω δοκιμάσει πολλούς τρόπους για ajax file upload και με κανένα δεν ήμουν ποτέ ευχαριστημένος. Ένας από αυτούς τους τρόπους που δοκίμασα, χρειάστηκε ακόμα και η εγκατάσταση του php extension uploadprogress. Σε συνδιασμό με css replace στο input file controll είχα ένα ικανοποιητικό αποτέλεσμα για asynchronous file upload. Τελικά κατέληξα να δουλεύω με το uplodify ένα jquery plugin χωρίς να μου αρέσει πολύ.
Τον τελευταίο καιρό ασχολήθηκα ξανά με το θέμα και βρήκα ένα πολύ ενδιαφέρων project το swfupload, το οποίο το μόνο του μειονέκτημα είναι ότι δεν είναι jquery plugin. Δεν πειράζει όμως γιατί βρήκα plugin σε jquery που το κάνει port. To swfupload είναι πολύ κάλο γιατί έχει υποστήριξη για multiple files, filter για τύπο αρχείων (πχ *.pdf), button image replace (hover,disable,…), και πολύ καλή διαχείριση των events. Η υλοποίηση του είναι σε flash, έτσι υπάρχει πληροφορία κατά το upload ποσά bytes έχουν ανεβεί. Το σημαντικότερο που με ενθουσίασε είναι ότι μπορείς να ελέγξεις πότε θα ξεκινήσει το upload, δηλαδή διαχωρίζει το select file, από το upload file event.
Όλα τα υπόλοιπα που έχω δοκιμάσει, μόλις επιλέξεις ένα αρχείο, ανεβαίνει κατευθείαν στον server, με αποτέλεσμα να πρέπει να αποθήκευσεις το αρχείο κάπου temporarily ώστε μετά την ολοκλήρωση της φόρμας να μπορείς να το χρησιμοποιήσεις. Αυτό χρειάζεται μέθοδο για file garbage collection σε περίπτωση που δεν ολοκληρωθεί η αποστολή της φόρμας για να σβήνει τα άχρηστα αρχεία.
Σε συνδυασμό με το jQuery progress bar το swfupload γίνεται το απόλυτο tool για upload files. Ας το δούμε λίγο στην πράξη. Θα φτιάξουμε μια φόρμα, με ένα πεδίο, και με επιλογή για 4 text αρχεία (.txt).
<?php session_start(); ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Upload files</title> <link href="main.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/jquery.progressbar.js"></script> <script type="text/javascript" src="js/swfupload.js"></script> <script type="text/javascript" src="js/jquery.swfupload.js"></script> <script type="text/javascript">var SID="<?php echo session_id();?>";</script> <script type="text/javascript" src="js/main.js"></script> </head> <body> <form action="" id="form" method="post"> <fieldset> <label for="email">EMAIL</label> <input type="text" name="email" id="email" value="" /> <div id="upload_form"> Files (max 4): <div id="custom-queue"></div> <input id="btn_file" type="button" /> </div> <br/> <div id="bar_file" style="display:none"></div> <div id="ajax_loader" style="display:none"><img src="img/ajax-loader.gif" alt="loader" /></div> <input id="submit_btn" type="submit" value="Submit"/> </fieldset> </form> </body> </html>
Στο παραπάνω παράδειγμα χρειάζεται στο php.ini η μεταβλητή session.auto_start off γιατί στην συνέχεια θα χρειαστούμε να περάσουμε στο upload script το session_id, ώστε να έχουμε μια σύνδεση του αποθηκευμένου αρχείου με το submit form script. Η σελίδα μας φορτώνει όλα τα απαραίτητα javascript libs καθώς και το main.js, όπου εκεί βρίσκετε ο κύριος κώδικας. Η φόρμα μας έχει το “custom-queue” div, οπού εκεί θα υποδεχθεί τα επιλεγμένα αρχεία, το “btn_file” button όπου εκεί θα φορτωθεί ο κώδικας του swfupload, και 2 hidden div όπου το 1ο έχει το progress bar για το upload των αρχείων και το 2o έχει ένα ajax loader image το οποίο θα εμφανιστεί κατά την αποστολή της φόρμας.
<?php session_id($_GET['SID']); session_start(); $dest='upload_files/'.$_GET['SID'].'/'; if (!file_exists($dest)) mkdir($dest); $dest.=$_FILES['Filedata']['name']; move_uploaded_file($_FILES['Filedata']['tmp_name'],$dest); die($_FILES['Filedata']['size']); ?>
Αυτό είναι ένα απλό upload script που η μόνη του ιδιαιτερότητα είναι ότι χρησιμοποιεί την μεταβλητή $_GET['SID'] για να ξεκινήσει το ίδιο session και για να αποθυκεύσει το αρχείο σε folder ώστε στην συνέχεια να είναι προσπελάσιμο από το submit form script. Στο παράδειγμά μας κάνει create ένα folder με όνομα το $_GET['SID'], και εκεί μέσα βάζει τα αρχεία από το upload. Όσα αρχεία επιλέγουμε τότε φορές θα εκτελεστεί το upload script. Στο τέλος τυπώνουμε το filezize, ώστε αν κάτι έχει γίνει λάθος το filesize θα είναι 0 και το swfupload θα προκαλέσει error event. Περισσότερα σχετικά με το error handling μπορείτε να δείτε στο manual του swfupload.
<?php
session_start();
$ret=array('success'=>true);
// TODO:
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/json');
echo json_encode($ret);
?>
Το submit post form είναι ένα απλό ajax script όπου μπορεί να γυρίσει μεταβλητές σε json, όπως success κτλ. Εδώ χρειάζεται κάποιος επιπλέον κώδικας για να σβήνει τα αρχεία αν δεν ολοκληρωθεί η διαδικασία (είχα αναφέρει παραπάνω για file garbage collection), εδώ όμως όπως βλέπετε είναι ποιο απλή η διαδικασία. Τα αρχεία τώρα μπορούμε να τα βρούμε πολύ εύκολα γιατί όπως είπαμε και παραπάνω τα έχουμε αποθυκεύσει με βάση το session_id.
$(document).ready(function(){
$("#upload_form").swfupload({
upload_url : "upload?SID="+SID,
flash_url : "swfupload.swf",
file_size_limit : "10 MB",
file_upload_limit : "0",
file_queue_limit: "4",
file_types: "*.txt",
file_types_description: "Text files",
button_image_url : "img/upload.png",
button_width : 88,
button_height : 22,
button_placeholder : $("#btn_file")[0],
button_window_mode : SWFUpload.WINDOW_MODE.TRANSPARENT
})
.bind("fileQueued", function(event, file){
$("#custom-queue").append('<div id="file_'+file.id+'" title="'+file.name+
'">'+file.name+'<span> (<a title="remove file" onclick="return removeFile(\''+file.id+'\')" href="#remove">x</a>)</span></div>');
})
.bind('uploadComplete', function(event, file){
// upload has completed, lets try the next one in the queue
$("#file_"+file.id).removeClass('file-progress').addClass('file-done');
if ($('.file-queue').length==$('.file-done').length) {
$("#bar_file").hide();
$("#ajax_loader").show();
$.ajax({
type: "POST",
url: "submit.php",
data: $("#form").serialize(),
dataType: "json",
success: function(data) {
$("#ajax_loader").fadeOut();
$("#bar_file").html('<strong>Submit completed</strong>').fadeIn();
}
});
}
else {
$(this).swfupload('startUpload');
}
})
.bind("uploadStart", function(event, file){
$("#file_"+file.id).addClass('file-progress');
$("#bar_file").progressBar(0);
})
.bind("uploadProgress", function(event, file, bytesLoaded, bytesTotal){
var percentage = Math.floor(100 * parseInt(bytesLoaded) / parseInt(bytesTotal));
$("#bar_file").progressBar(percentage);
})
.bind("uploadError", function(event, file, errorCode, message){
if (errorCode === SWFUpload.UPLOAD_ERROR.FILE_CANCELLED) {
$("#file_"+file.id).fadeOut('normal',function() { $("#file_"+file.id).remove(); });
}
});
$("#bar_file").progressBar({
boxImage : 'img/progressbar.gif',
barImage : 'img/progressbg_orange.gif'
});
$("#form").submit(function() {
if ($('.file-queue').length==0) {
alert('You must select a file!');
}
else {
$("#bar_file").show();
$("#submit_btn").hide();
$("#SWFUpload_0").width(0);
$("#upload_form").swfupload('startUpload');
}
return false;
});
});
function removeFile(id) {
$("#upload_form").swfupload('cancelUpload',id,true);
return false;
}
Και πάμε στο σημαντικότερο μέρος του project που είναι javascript. Εδώ έχουμε όλα τα bind events για το swfupload, από το load, το select files μέχρι και το submit της φόρμας. Ας τα πάρουμε από την αρχή για να τα δούμε αναλυτικά.
$("#upload_form").swfupload({
upload_url : "upload?SID="+SID,
flash_url : "swfupload.swf",
file_size_limit : "10 MB",
file_upload_limit : "0",
file_queue_limit: "4",
file_types: "*.txt",
file_types_description: "Text files",
button_image_url : "img/upload.png",
button_width : 88,
button_height : 22,
button_placeholder : $("#btn_file")[0],
button_window_mode : SWFUpload.WINDOW_MODE.TRANSPARENT
})
Εδώ φορτώνουμε το swfupload βάζοντας το url από το upload.php (περνάμε και το SID που το έχουμε βάλει σε μια javascript μεταβλητή και περιέχει το session_id, το url που βρίσκεται το swfupload.swf, το μέχρι πόσα αρχεία θέλουμε, τι μέγεθος να έχει το καθένα, και τι τύπο (πχ μπορούμε να βάλουμε “*.pdf,*.odt”). Τέλος το image αρχείο, με το μέγεθος του και σε ποιο element να γίνει bind.
.bind("fileQueued", function(event, file){
$("#custom-queue").append('<div id="file_'+file.id+'" title="'+file.name+
'">'+file.name+'<span> (<a title="remove file" onclick="return removeFile(\''+file.id+'\')" href="#remove">x</a>)</span></div>');
})
Και κάπου εδώ αρχίζουν τα events, 1ο event το “fileQueued”, το οποίο εκτελείτε κατά την επιλογή αρχείων. Όσα αρχεία επιλεγούν τόσες φορές θα εκτελεστεί. Εδώ το μόνο που θέλουμε να κάνουμε είναι να εμφανίσουμε τα επιλεγμένα αρχεία, στο div “custom-queue”. Τέλος βάζουμε και ένα (x) για αφαίρεσή αρχείου από την λίστα. Για την αφαίρεσή χρειαζόμαστε το file.id. Στο file.name έχουμε το όνομα του αρχείου.
.bind("uploadStart", function(event, file){
$("#file_"+file.id).addClass('file-progress');
$("#bar_file").progressBar(0);
})
.bind("uploadProgress", function(event, file, bytesLoaded, bytesTotal){
var percentage = Math.floor(100 * parseInt(bytesLoaded) / parseInt(bytesTotal));
$("#bar_file").progressBar(percentage);
})
.bind("uploadError", function(event, file, errorCode, message){
if (errorCode === SWFUpload.UPLOAD_ERROR.FILE_CANCELLED) {
$("#file_"+file.id).fadeOut('normal',function() { $("#file_"+file.id).remove(); });
}
});
Τα επόμενα 3 event έχουν να κάνουν με το upload. Στο “uploadStart” αλάζουμε το class στο αρχείο που είναι να ανέβει ώστε να φαίνεται ποιο είναι, και μηδενίζουμε το progress bar. Και αυτό το event εκτελείτε τόσες φορές όσα τα αρχεία που έχουμε επιλέξει. To “uploadProgress” μας δείχνει το progress του upload. Τέλος το “uploadError” εκτελείτε σε περίπτωση σφάλματος. Εδώ το χρησιμοποιούμε και για την αφαίρεση ενός αρχείου από την λίστα.
.bind('uploadComplete', function(event, file){
// upload has completed, lets try the next one in the queue
$("#file_"+file.id).removeClass('file-progress').addClass('file-done');
if ($('.file-queue').length==$('.file-done').length) {
$("#bar_file").hide();
$("#ajax_loader").show();
$.ajax({
type: "POST",
url: "submit.php",
data: $("#form").serialize(),
dataType: "json",
success: function(data) {
$("#ajax_loader").fadeOut();
$("#bar_file").html('<strong>Submit completed</strong>').fadeIn();
}
});
}
else {
$(this).swfupload('startUpload');
}
})
Το “uploadComplete” είναι το event που εκτελείτε μετά την ολοκλήρωση του upload κάθε αρχείου. Εδώ ελέγχουμε και αν έχει άλλο δίνουμε εντολή για το επόμενο, κ.ο.κ. Αν δεν έχει άλλο κάνουμε submit την φόρμα μας. Για τον έλεγχο αν υπάρχει άλλο αρχείο στην λίστα, χρησιμοποιούμε την class “file-done” την οποία την προσθέτουμε στο div “file-queue” μετά το τέλος του upload. Οπότε με ένα απλό έλεγχο στο length του “file-queue” και του “file-done”, αν είναι ίσα δηλαδή, δεν έχουμε άλλα αρχεία.
$("#form").submit(function() {
if ($('.file-queue').length==0) {
alert('You must select a file!');
}
else {
$("#bar_file").show();
$("#submit_btn").hide();
$("#SWFUpload_0").width(0);
$("#upload_form").swfupload('startUpload');
}
return false;
});
Τέλος αλλάζουμε το submit event της φόρμας ώστε να γίνεται πρώτα το upload. Δίνουμε στο swfupload object width 0, για να εξαφανιστεί το κουμπί upload, κάνουμε hide το submit button, εμφανίζουμε το progress bar και ξεκινάμε το 1o upload, δίνοντάς εντολή στο flash.
Αυτό ήταν ένα απλό παράδειγμα σε σχέση με τις δυνατότητες που παρέχει το swfupload, το οποίο σε συνδυασμό και με άλλα jquery plugins, το αποτέλεσμα μπορεί να είναι καλύτερο.
Όλος ο παραπάνω κώδικας βρίσκεται εδώ και μπορείτε να το κατεβάσατε ελεύθερα.




