- Overview
- Documents
In this tutorial we are going to create an AJAX file upload form, that will let visitors upload files from their browsers with drag/drop or by selecting them individually. For the purpose, we will combine the powerful jQuery File Upload plugin with the neat jQuery Knob to present a slick CSS3/JS driven interface.
The HTML
As usual, we will start off with a basic HTML5 document:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>Mini Ajax File Upload Form</title> <!-- Google web fonts --> <link href="http://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700" rel='stylesheet' /> <!-- The main CSS file --> <link href="assets/css/style.css" rel="stylesheet" /> </head> <body> <form id="upload" method="post" action="upload.php" enctype="multipart/form-data"> <div id="drop"> Drop Here <a>Browse</a> <input type="file" name="upl" multiple /> </div> <ul> <!-- The file uploads will be shown here --> </ul> </form> <!-- JavaScript Includes --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script src="assets/js/jquery.knob.js"></script> <!-- jQuery File Upload Dependencies --> <script src="assets/js/jquery.ui.widget.js"></script> <script src="assets/js/jquery.iframe-transport.js"></script> <script src="assets/js/jquery.fileupload.js"></script> <!-- Our main JS file --> <script src="assets/js/script.js"></script> </body> </html>
In the head of the document, I have included two fonts from Google Webfonts, and before the closing </body>tag you can see a number of JavaScript libraries. These are the jQuery library, the jQuery Knob plugin and the dependencies for the jQuery File Upload plugin.
The main element on the page is the #upload form. Inside it is the #drop div (which accepts drag/drop uploads) and an unordered list. This list will hold a li item for each of the transferred files. You can see the markup generated for a file upload below:
<li class="working"> <input type="text" value="0" data-width="48" data-height="48" data-fgColor="#0788a5" data-readOnly="1" data-bgColor="#3e4043" /> <p>Sunset.jpg <i>145 KB</i></p> <span></span> </li>
The input item in the snippet above is hidden with CSS. Its only purpose is to initialize the jQuery Knob plugin, which will output a pretty canvas-based knob control. The input has a number of data-* attributes that modify the appearance of the knob. Later, when we listen for the file upload progress, we will update the value of this input which will cause the knob to get redrawn. The span holds the icon to the right; this can either be a check mark or a red cross.
The jQuery Code
There are two ways a visitor can upload files with this form:
- By dropping them on the #drop div (in all browsers except IE);
- By clicking the browse button. This will simulate a click on the hidden file input, which will bring the system’s file browsing window. Notice that the file input has the multiple parameter set, which will allow more than one file to be selected at a given time (the files will still be uploaded individually though!).
The default behavior of the plugin is to place the files in a queue, but we will make the files upload automatically when they are dropped/selected, which will make the experience more straightforward. You can see the JS below:
assets/js/script.js
$(function(){ var ul = $('#upload ul'); $('#drop a').click(function(){ // Simulate a click on the file input button // to show the file browser dialog $(this).parent().find('input').click(); }); // Initialize the jQuery File Upload plugin $('#upload').fileupload({ // This element will accept file drag/drop uploading dropZone: $('#drop'), // This function is called when a file is added to the queue; // either via the browse button, or via drag/drop: add: function (e, data) { var tpl = $('<li class="working"><input type="text" value="0" data-width="48" data-height="48"'+ ' data-fgColor="#0788a5" data-readOnly="1" data-bgColor="#3e4043" /><p></p><span></span></li>'); // Append the file name and file size tpl.find('p').text(data.files[0].name) .append('<i>' + formatFileSize(data.files[0].size) + '</i>'); // Add the HTML to the UL element data.context = tpl.appendTo(ul); // Initialize the knob plugin tpl.find('input').knob(); // Listen for clicks on the cancel icon tpl.find('span').click(function(){ if(tpl.hasClass('working')){ jqXHR.abort(); } tpl.fadeOut(function(){ tpl.remove(); }); }); // Automatically upload the file once it is added to the queue var jqXHR = data.submit(); }, progress: function(e, data){ // Calculate the completion percentage of the upload var progress = parseInt(data.loaded / data.total * 100, 10); // Update the hidden input field and trigger a change // so that the jQuery knob plugin knows to update the dial data.context.find('input').val(progress).change(); if(progress == 100){ data.context.removeClass('working'); } }, fail:function(e, data){ // Something has gone wrong! data.context.addClass('error'); } }); // Prevent the default action when a file is dropped on the window $(document).on('drop dragover', function (e) { e.preventDefault(); }); // Helper function that formats the file sizes function formatFileSize(bytes) { if (typeof bytes !== 'number') { return ''; } if (bytes >= 1000000000) { return (bytes / 1000000000).toFixed(2) + ' GB'; } if (bytes >= 1000000) { return (bytes / 1000000).toFixed(2) + ' MB'; } return (bytes / 1000).toFixed(2) + ' KB'; } });
The jQuery File Upload library comes with its own jQuery UI-powered design that you can use straight away. However, because we need an entirely custom interface, we will make use of the basic version of the plugin, which doesn’t include an interface. To make it work, we are passing a number of configuration options / callbacks. In the code above, these are:
- dropZone – This property holds the jQuery selector of the element which will act as a drop target. Files dropped upon it will be added to the upload queue.
- add – This callback function is called whenever a file is added to the queue. Inside it, we create the HTML markup that will represent the file, add it to the UL and trigger the data.submit() method. This will cause the added file to be directly uploaded without waiting.
- progress – This callback is executed by the plugin every 100ms (configurable). The second argument (the data attribute) holds the file size and how many bytes have been transferred. This allows us to calculate a percentage, and subsequently update the hidden input element, which in turn updates the knob.
- fail – This callback function is executed if there is a problem with your PHP script. This would most likely mean that upload.php is missing or throwing some kind of error (use your web browser’s inspector to debug any potential problems here).
See a full list of all the available configuration options in this page. I have included more resources about the plugin in the Resources and further reading section at the end of this tutorial.
The data.context property is preserved between the method calls of the plugin. This way we know which LI item we should update in the progress and fail events.
The PHP Script
jQuery File Upload also comes with a powerful PHP script for handling file uploads that you can put on your server, but for this tutorial, we will build our own. The file uploads sent by the plugin are practically the same as a regular form upload – you can access information about the uploads through the $_FILES array:
<?php // A list of permitted file extensions $allowed = array('png', 'jpg', 'gif','zip'); if(isset($_FILES['upl']) && $_FILES['upl']['error'] == 0){ $extension = pathinfo($_FILES['upl']['name'], PATHINFO_EXTENSION); if(!in_array(strtolower($extension), $allowed)){ echo '{"status":"error"}'; exit; } if(move_uploaded_file($_FILES['upl']['tmp_name'], 'uploads/'.$_FILES['upl']['name'])){ echo '{"status":"success"}'; exit; } } echo '{"status":"error"}'; exit;
As I mentioned further back, although we may select a bunch of files at once, they are uploaded one by one. This makes it even easier to handle them with our PHP script. Currently, the files are simply moved to the uploads folder, but you can extend it by adding authentication or creating records in your database.