FancyUpload - Swiff meets Ajax (v3.0)
Swiff meets Ajax for powerful and elegant uploads.
FancyUpload is a file-input replacement which features an unobtrusive, multiple-file selection
menu and queued upload with an animated progress bar. It is easy to setup, is server independent,
completely styleable via CSS and XHTML and uses MooTools to work in all modern browsers.
Showcase “Queued Photo Uploader”
Showcase with converted FancyUpload.
The Action Happens Here
Don’t think that the uploader can handle only images! But since this showcase is called “Queued Photo Uploader”, the selectable file-types are limited to images. Check the showcase JavaScript shown under this paragraph for more options.
What happens?
The PHP script does not save the files but logs every request. Check the Firebug console for event debugging logs.
- Select one or more images.
- The images are uploaded one by one, overall progress and file progress is updated.
- The server-side PHP script logs the upload (see .log).
- A JSON response is returned, containing some information about the images like
mime-type, width and height. - This information is added to the completed file element. We would link the uploaded image, but we don’t save any files in this showcase.
Do Not:
- Copy this example and ask why it doesn’t upload the files. It only logs the uploaded images!
- Copy this example without changing the XHTML, the CSS or the images!
- Skip all validation on the server and blindly move the files!
Please Do:
- Take this showcase as a code example about processing and validating the uploaded file and create your own server code.
- Adapt the XHTML, the CSS and the images to your needs and share them.
JavaScript & MooTools
/** * FancyUpload Showcase * * @license MIT License * @author Harald Kirschner <mail [at] digitarald [dot] de> * @copyright Authors */ window.addEvent('domready', function() { // wait for the content // our uploader instance var up = new FancyUpload2($('demo-status'), $('demo-list'), { // options object // we console.log infos, remove that in production!! verbose: true, // url is read from the form, so you just have to change one place url: $('form-demo').action, // path to the SWF file path: '../../source/Swiff.Uploader.swf', // remove that line to select all files, or edit it, add more items typeFilter: { 'Images (*.jpg, *.jpeg, *.gif, *.png)': '*.jpg; *.jpeg; *.gif; *.png' }, // this is our browse button, *target* is overlayed with the Flash movie target: 'demo-browse', // graceful degradation, onLoad is only called if all went well with Flash onLoad: function() { $('demo-status').removeClass('hide'); // we show the actual UI $('demo-fallback').destroy(); // ... and hide the plain form // We relay the interactions with the overlayed flash to the link this.target.addEvents({ click: function() { return false; }, mouseenter: function() { this.addClass('hover'); }, mouseleave: function() { this.removeClass('hover'); this.blur(); }, mousedown: function() { this.focus(); } }); // Interactions for the 2 other buttons $('demo-clear').addEvent('click', function() { up.remove(); // remove all files return false; }); $('demo-upload').addEvent('click', function() { up.start(); // start upload return false; }); }, // Edit the following lines, it is your custom event handling /** * Is called when files were not added, "files" is an array of invalid File classes. * * This example creates a list of error elements directly in the file list, which * hide on click. */ onSelectFail: function(files) { files.each(function(file) { new Element('li', { 'class': 'validation-error', html: file.validationErrorMessage || file.validationError, title: MooTools.lang.get('FancyUpload', 'removeTitle'), events: { click: function() { this.destroy(); } } }).inject(this.list, 'top'); }, this); }, /** * This one was directly in FancyUpload2 before, the event makes it * easier for you, to add your own response handling (you probably want * to send something else than JSON or different items). */ onFileSuccess: function(file, response) { var json = new Hash(JSON.decode(response, true) || {}); if (json.get('status') == '1') { file.element.addClass('file-success'); file.info.set('html', '<strong>Image was uploaded:</strong> ' + json.get('width') + ' x ' + json.get('height') + 'px, <em>' + json.get('mime') + '</em>)'); } else { file.element.addClass('file-failed'); file.info.set('html', '<strong>An error occured:</strong> ' + (json.get('error') ? (json.get('error') + ' #' + json.get('code')) : response)); } }, /** * onFail is called when the Flash movie got bashed by some browser plugin * like Adblock or Flashblock. */ onFail: function(error) { switch (error) { case 'hidden': // works after enabling the movie and clicking refresh alert('To enable the embedded uploader, unblock it in your browser and refresh (see Adblock).'); break; case 'blocked': // This no *full* fail, it works after the user clicks the button alert('To enable the embedded uploader, enable the blocked Flash movie (see Flashblock).'); break; case 'empty': // Oh oh, wrong path alert('A required file was not found, please be patient and we fix this.'); break; case 'flash': // no flash 9+ :( alert('To enable the embedded uploader, install the latest Adobe Flash plugin.') } } }); });
XHTML Markup
<form action="../script.php" method="post" enctype="multipart/form-data" id="form-demo"> <fieldset id="demo-fallback"> <legend>File Upload</legend> <p> This form is just an example fallback for the unobtrusive behaviour of FancyUpload. If this part is not changed, something must be wrong with your code. </p> <label for="demo-photoupload"> Upload a Photo: <input type="file" name="Filedata" /> </label> </fieldset> <div id="demo-status" class="hide"> <p> <a href="#" id="demo-browse">Browse Files</a> | <a href="#" id="demo-clear">Clear List</a> | <a href="#" id="demo-upload">Start Upload</a> </p> <div> <strong class="overall-title"></strong><br /> <img src="../../assets/progress-bar/bar.gif" class="progress overall-progress" /> </div> <div> <strong class="current-title"></strong><br /> <img src="../../assets/progress-bar/bar.gif" class="progress current-progress" /> </div> <div class="current-text"></div> </div> <ul id="demo-list"></ul> </form>
PHP Script
<?php /** * Swiff.Uploader Example Backend * * This file represents a simple logging, validation and output. * * * WARNING: If you really copy these lines in your backend without * any modification, there is something seriously wrong! Drop me a line * and I can give you a good rate for fancy and customised installation. * * No showcase represents 100% an actual real world file handling, * you need to move and process the file in your own code! * Just like you would do it with other uploaded files, nothing * special. * * @license MIT License * * @author Harald Kirschner <mail [at] digitarald [dot] de> * @copyright Authors * */ /** * Only needed if you have a logged in user, see option appendCookieData, * which adds session id and other available cookies to the sent data. * * session_name('SID'); // whatever your session name is, adapt that! * session_start(); */ // Request log /** * You don't need to log, this is just for the showcase. Better remove * those lines for production since the log contains detailed file * information. */ $result = array(); $result['time'] = date('r'); $result['addr'] = substr_replace(gethostbyaddr($_SERVER['REMOTE_ADDR']), '******', 0, 6); $result['agent'] = $_SERVER['HTTP_USER_AGENT']; if (count($_GET)) { $result['get'] = $_GET; } if (count($_POST)) { $result['post'] = $_POST; } if (count($_FILES)) { $result['files'] = $_FILES; } // we kill an old file to keep the size small if (file_exists('script.log') && filesize('script.log') > 102400) { unlink('script.log'); } $log = @fopen('script.log', 'a'); if ($log) { fputs($log, print_r($result, true) . "\n---\n"); fclose($log); } // Validation $error = false; if (!isset($_FILES['Filedata']) || !is_uploaded_file($_FILES['Filedata']['tmp_name'])) { $error = 'Invalid Upload'; } /** * You would add more validation, checking image type or user rights. * if (!$error && $_FILES['Filedata']['size'] > 2 * 1024 * 1024) { $error = 'Please upload only files smaller than 2Mb!'; } if (!$error && !($size = @getimagesize($_FILES['Filedata']['tmp_name']) ) ) { $error = 'Please upload only images, no other files are supported.'; } if (!$error && !in_array($size[2], array(1, 2, 3, 7, 8) ) ) { $error = 'Please upload only images of type JPEG, GIF or PNG.'; } if (!$error && ($size[0] < 25) || ($size[1] < 25)) { $error = 'Please upload an image bigger than 25px.'; } */ // Processing /** * Its a demo, you would move or process the file like: * * move_uploaded_file($_FILES['Filedata']['tmp_name'], '../uploads/' . $_FILES['Filedata']['name']); * $return['src'] = '/uploads/' . $_FILES['Filedata']['name']; * * or * * $return['link'] = YourImageLibrary::createThumbnail($_FILES['Filedata']['tmp_name']); * */ if ($error) { $return = array( 'status' => '0', 'error' => $error ); } else { $return = array( 'status' => '1', 'name' => $_FILES['Filedata']['name'] ); // Our processing, we get a hash value from the file $return['hash'] = md5_file($_FILES['Filedata']['tmp_name']); // ... and if available, we get image data $info = @getimagesize($_FILES['Filedata']['tmp_name']); if ($info) { $return['width'] = $info[0]; $return['height'] = $info[1]; $return['mime'] = $info['mime']; } } // Output /** * Again, a demo case. We can switch here, for different showcases * between different formats. You can also return plain data, like an URL * or whatever you want. * * The Content-type headers are uncommented, since Flash doesn't care for them * anyway. This way also the IFrame-based uploader sees the content. */ if (isset($_REQUEST['response']) && $_REQUEST['response'] == 'xml') { // header('Content-type: text/xml'); // Really dirty, use DOM and CDATA section! echo '<response>'; foreach ($return as $key => $value) { echo "<$key><![CDATA[$value]]></$key>"; } echo '</response>'; } else { // header('Content-type: application/json'); echo json_encode($return); } ?>
CSS Stylesheet
/** * FancyUpload Showcase * * @license MIT License * @author Harald Kirschner <mail [at] digitarald [dot] de> * @copyright Authors */ /* CSS vs. Adblock tabs */ .swiff-uploader-box a { display: none !important; } /* .hover simulates the flash interactions */ a:hover, a.hover { color: red; } #demo-status { padding: 10px 15px; width: 420px; border: 1px solid #eee; } #demo-status .progress { background: url(../../assets/progress-bar/progress.gif) no-repeat; background-position: +50% 0; margin-right: 0.5em; vertical-align: middle; } #demo-status .progress-text { font-size: 0.9em; font-weight: bold; } #demo-list { list-style: none; width: 450px; margin: 0; } #demo-list li.validation-error { padding-left: 44px; display: block; clear: left; line-height: 40px; color: #8a1f11; cursor: pointer; border-bottom: 1px solid #fbc2c4; background: #fbe3e4 url(assets/failed.png) no-repeat 4px 4px; } #demo-list li.file { border-bottom: 1px solid #eee; background: url(assets/file.png) no-repeat 4px 4px; overflow: auto; } #demo-list li.file.file-uploading { background-image: url(assets/uploading.png); background-color: #D9DDE9; } #demo-list li.file.file-success { background-image: url(assets/success.png); } #demo-list li.file.file-failed { background-image: url(assets/failed.png); } #demo-list li.file .file-name { font-size: 1.2em; margin-left: 44px; display: block; clear: left; line-height: 40px; height: 40px; font-weight: bold; } #demo-list li.file .file-size { font-size: 0.9em; line-height: 18px; float: right; margin-top: 2px; margin-right: 6px; } #demo-list li.file .file-info { display: block; margin-left: 44px; font-size: 0.9em; line-height: 20px; clear } #demo-list li.file .file-remove { clear: right; float: right; line-height: 18px; margin-right: 6px; }
This example and the accompanying sources/assets are © 2008-2009 by Harald Kirschner and available under The MIT License. For debugging and profiling the scripts and their markup download Firefox and use addons like Firebug and Web Developer Toolbar.
1231 Comments
Please use the support forums for discussing the project, asking questions or posting bug-fixes!
i am not great with the javascript, but fairly dangerous with most else. i am working your fancy uploader into my site where it renames and moves the files, when they "Remove" I need to destroy those files and make other changes to data rows. I know it calls a function called "onFileRemove" but I am not sure where it fires off identifying data to and how to fire off php with that click of the "Remove" link. Can you give me some help? Thanks, am intermediate, been working to integrate this for a day now LOL.