Mini AJAX File Upload Form

Mini AJAX File Upload Form

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.

Mini AJAX File Upload Form

Mini AJAX File Upload Form

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.

We’re done!

I hope that you find this ajax file upload form useful! If you have suggestions or questions, leave them in our comment area.

Resources and further reading

Join our newsletter and get our PSDs!19,498 people learn about HTML5, JS and more. Join them!

by Martin Angelov

Martin is a web developer with an eye for design from Bulgaria. He founded Tutorialzine in 2009 and it still is his favorite side project.

84 Comments

  1. Minime says:

    Thats just what i needed! Thank youuuuuuuuuuuuuu

  2. Kaan Erkol says:

    Very Cool

  3. Mayuresh says:

    This is fantastic !!! Just tried a demo, and it works magnificently

  4. Freddy says:

    You have the strange knack of publishing tutorials at _exactly_ the right time! Great tutorial!

  5. Linas says:

    Great! Exactly what I needed. Thank you.

  6. NICO says:

    Amazing as always!
    Thx, man!

  7. Fernando says:

    Another master piece! Thank you so much

  8. Luciano says:

    Fantastic code! I have a problem. I can't upload any file more than 4mb. Any solution for that? Thanks a lot!!

    1. SteveK says:

      Luciano,

      You will need to add the following code to your .htaccess file. You can also change it in your php.ini file.

      php_value upload_max_filesize 20M
      php_value post_max_size 20M
      php_value max_execution_time 200
      php_value max_input_time 200

      I set mine to 20mb

  9. kwang says:

    Thanks ^____^

  10. Dennis says:

    The point of jquery is to support older browsers. You could have done a modern html5 file upload instead of adding all that jquery junk for no good reason.

    I don't like those old ie browsers either, but they are out there and your demo does not work with any of them.

  11. miniMAC says:

    What i want! Godo explanation and good example! Very very good jobs guys

  12. miniMAC says:

    Hello,

    is possible to have only one file upload?

    now the code support multiple uploads:
    <input type="file" name="upl" multiple />

    i've tried without "multiple" but not working...

  13. Mikael S says:

    Great tutorial. I needed this. Thanks.

    Is there a way to let the user submit e.g. name and email information after the download?

  14. Max says:

    seems like the demo doesnt work in IE9?!

    it adds the pictures to the list but doesnt upload them (tested it on my webserver too). Is IE8/9 supported and is there a way the make the upload form work in IE?

    Works fine in Firefox and Chrome!

  15. Igor says:

    Great tutorial!

    I need to implement this in ASP.NET. I would like you to explain to me briefly what your file 'php' makes when an error occurs or when when everything right ... it returns a string value? '{"status": "error"}', or '{"status": "success"}';? The big question is ... what kind of return? I do not know php --- Sorry for my english! (Google Translator)

  16. Luciano says:

    In Firefox and Chrome work fine, but in IE don't work anything. Files aren't uploaded with the browse button. How can I solve it?

    1. Martin Angelov says:

      Drag/drop doesn't work in any version of IE, but the browse button works in IE 10 (haven't tested it in 9).

  17. Mustafa ismail says:

    Very Cool

  18. Alf says:

    I created an Ajax File Uploader also using fileupload.js, but with support to upload to S3 directly via CORS. NodeJS is used as the backend for generating the signature.

    If anyone is interested, the project and demo is located at https://github.com/alfg/dropdot

  19. Marcelo Barros says:

    Hi Martin!

    How to put an image preview on each upload?

    Anyone know how to apply that on this example?

    Thanks

    1. Martin Angelov says:

      The jQuery file upload plugin that I am using for the tutorial has support for image previews. Making it work on this example shouldn't be much effort.

      1. Paul says:

        Well, I was trying to do that myself, but I failed. I would love if you could post a tips :)

  20. BiBi says:

    very very very cool, thank you very much, you are my idol :love

  21. Slava0008 says:

    In Opera (12.15) don't work button BROWSE, drag/drop fine work.

  22. Teilmann says:

    Hello!

    Great upload, but i cannot get it to upload .jpg or .jpeg files. Even the demo you download cannot upload .jpg ? All other files work fine. Please halp

  23. marc says:

    I can't found the files i upload on the uploads folder.. =(

  24. SteveK says:

    Hi Martin!

    This uploader works great! No problem implementing it. Thank you.

    I would like to have a page reload after all files have finished uploading. I thought I had the answer, but after the first file finished loading it would cancel any other files being uploaded so that didn't work. Is there a way to have the page reload after all files have been uploaded? If so, how does it work?

  25. marc says:

    Hello,
    That's a very nice plugin.
    I understand we need a database to use this plugin in our website.
    In witch file we need to conect with. It's not detailed on the tutorial.
    I can't found the explanation about this.

    Thanks,

  26. Kiel Lago says:

    How can i restrict this to pdf uploads only?

  27. Great tutorial.

    Only a little problem... When you upload files with the same name, the files are overwritten.

    It's a very big problem when you upload photos from an iPhone, for example, where all the photos has the same_name.jpg.

    Any tip to fix this?

    Thanks in advance!

    1. Martin Angelov says:

      You will have to assign a unique name in the PHP upload handling script for each file. You can use something like md5_file to generate a unique name for every file. However for real scenarios it will be best to use a database. You can then use the auto increment id generated by the table as the filename.

      1. Frank says:

        Or just concatenate the name with 'time()' php function

        http://php.net/manual/fr/function.time.php

    2. Frank says:

      modify upload.php at line 15,

      change:
      move_uploaded_file($_FILES['upl']['tmp_name'], 'uploads/'.$_FILES['upl']['name'])

      to:
      move_uploaded_file($_FILES['upl']['tmp_name'], 'uploads/'.$YOURnEWnAMEhERE)

      or something like this!! check:
      http://php.net/manual/fr/function.move-uploaded-file.php
      for more information about the function.

      really easy fix ;)

  28. Martin Månsson says:

    Hi Martin,

    Thanks for a great tutorial! When uploading multiple files, is there an easy solution to upload them one by one instead of uploading all of them at the same time?
    When i upload more than 3-4 images from an iPhone only 2-3 will finish and the rest just stops.

    Thanks,
    Martin

    1. Martin Månsson says:

      Found the solution! For those who want to know. Add the option sequentialUploads: true to the script.js when initializing the fileupload :)

  29. codeLikeAMan says:

    Great, great code. Love it. Question: I would like to associate the images uploaded with specific records submitted in a php form. Is there a way to do this?

    thanks great work

  30. marc says:

    If this file needs a database we will need to connect to the database from the php file.
    I can't see the code do to that in any file you provide.

    Can someone answer me, please?
    It's maybe a stupid question for you, but it's a real question for me.

  31. Shahrukh says:

    Hi. this is a very good scrip. But I am having trouble customizing a section where I want to check if I am uploading a file that is having size more that 10mb it will return an error message. I am having a hard time customizing it. Please help!!
    Thank You

  32. serge says:

    Hello,

    this is a great tool, it does exactly what is needed !! Congratulation

    Just one question, and forgive me because I am not a great specialist, how to process messages from the server side ? As an exemple, if the file is not an image, I want the server to issue a message (easy) which makes the red cross instead of the green mark. And also, where to program a javascript action when upload has been fiished ? Again, forgive me for the low level question, it should be simple...

  33. John says:

    Good script, although when a file gets the status of error because it's extention is not in the allowed list nothing happends, the file goes to upload (although never goes to the download folder). Shouldn't an error appear as it looks like it is uploaded

    1. Damir says:

      Replace code

      var jqXHR = data.submit();

      with this

       // Automatically upload the file once it is added to the queue
      var jqXHR = data.submit().success(function(result, textStatus, jqXHR){
      
      	var json = JSON.parse(result);
      	var status = json['status'];
      
      	if(status == 'error'){
      		data.context.addClass('error');
      	}
      
      	setTimeout(function(){
      		data.context.fadeOut('slow');
      	},3000);
      });
      
      1. Vch.Nazarov says:

        Thanks a lot!

      2. Andrea says:

        Thanks! now it works perfectly!

  34. Shayan says:

    Hi ! That was cool ! Thanks !

    But how can we show the uploaded file link ?!

  35. B says:

    I purchase fine uplpader. I would like this code to use. How can I make it fully work. Not just tje demo. Thank you in advance

  36. Rene says:

    Since you are using jQuery File Upload plugin from Sebastian Tschan, why doesn't the uploading part of this work in IE?

  37. George says:

    Hey,
    How can I restrict que uploading only when a submit button was clicked?
    Thanks again for this great tutorial.
    Keep 'em coming.

    1. Lobek says:

      For example:

      // Automatically upload the file once it is added to the queue
      $('#submit-test').click(function(){
      data.submit();
      });

      you must change too:
      if(tpl.hasClass('working')){
      data.submit().abort();
      }

      And add to your template
      <span id="submit-test">Wyślij pliki</span>

      Sorry for my bad English

  38. Burnside says:

    HI,

    I try to add a select folder chose, but when I submit, It ignore the select folder and upload nothing...

    Any idea.

  39. Abdo Belk says:

    This is really cool, that's what I was searching for !

  40. Lucas Leal says:

    Works great, but the "error" returned by PHP is not interpreted as error by jQuery which means that when the file was not uploaded or are not alowed *by extension* there is no error at all.

    What I am doing wrong ?

    1. I saw that too. I just added

      if(data.files[0]['type'] != 'image/png' && data.files[0]['type'] != 'image/jpg' && data.files[0]['type'] != 'image/jpeg'){
      alert("Only .png, .jpg, and .jpeg is allowed.");
      return;
      }

      On line 20 of script.js

  41. deno says:

    Thanks, I'm creating a prototype to demonstrate something with this functionality and a back end for file handling. You gave me what I needed.

  42. Chris Walker says:

    Hi Martin,

    Great code, really nice UX too.

    I was wondering if this could be hooked up to Codeigniter? I've battled for several days now to get CI to handle multiple uploads but I can't do it...

    Perhaps a new tutorial??

    Many thanks,
    Chris

  43. Alexey says:

    Hi Martin,
    Wonderful !
    The only thing I've noticed, it does not work in Safari, Browse button is not clickable
    Can you please comment on what could be the reason?

    Thank you,
    Alexey

  44. manguo says:

    Hi, I'm trying to customize the "progress" function in script.js, I want to add it a parameter, is anybody can tell me where the function "progress" is called?

  45. The browse button doesn't work on my Note II and someone above said it doesn't work on Opera. Any idea how to fix? Otherwise, great work dude! I've customized the crap outta this to make what I need, but now I've gotta find a way to limit to 1 upload for my video area. Photos are unlimited so It worked almost outta the box for that.

  46. David says:

    Great tutorial! I have learned a lot.
    I am trying to modify it and I am running into a little bit of problems. I am trying to have it upload quicktime files. (*.mov) In the PHP upload file in the $allowed variable I added 'mov' to the end. I tried to upload and the UI has it go through but it never uploads. It seems to be passing right through all of the if statements. It' seems to only happen on *.mov files. The upload did work with *.mp4 files. What am I missing?

    1. Martin Angelov says:

      It is probably too large. In php.ini there is a setting max_upload_filesize. Increase it to a value that would fit the files you'd like to upload.

    2. Stefan says:

      upload size and max post data can be set in .htaccess:
      php_value upload_max_filesize 10M
      php_value post_max_size 10M

  47. Hi there

    Love the tutorial and love the script. Only problem I had was, that if people upload files with the same name, that the original will be overwritten. I used it for bulk photo uploading, so it happened a lot that that files of cameras had the same name.

    Therefore I added a timestamp to the uploaded files and presto, the problem was solved.

    $timestamp = date("y-m-d-H-i-s");
    
    // A list of permitted file extensions
    $allowed = array('png', 'jpg', 'gif','zip', 'tif', 'tiff');
    
    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/'.$timestamp.'-'.$_FILES['upl']['name'])){
    		echo '{"status":"success"}';
    		exit;
    	}
    }
    
    echo '{"status":"error"}';
    exit;
    
  48. Amy says:

    This tutorial is great, except it doesn't work in IE9 or below. Any idea what the problem might be? All of the components seem to support IE 7+, but it doesn't work as a whole...

  49. geronim0 says:

    Hi,

    Thanks for great script! I have one issue with it. It looks like that your script does not return error when uploading files with extensions that are not permitted, like .php. It just informs about successful file upload like for allowed files.

    Is there any way to fix it?

  50. Mike says:

    Such a shame, nice plugin, but IE9/Opera and some mobile browsers are not supported

  51. Tom says:

    Hi there,

    well done! There is just a small issue with user interface. It is not a good idea to use "remove" button for upload state information. When file is uploaded clicking on a tick removes a file. What the hell? It is completely unexpected. My suggestion is to use cross always and use something else to indicate the state of upload.

    Tom

  52. poila says:

    I am using this Upload Form together with PHP in an existing WordPress project.
    A question regarding this Upload Form:

    Q.) I notice that inside the provided PHP script, it supposedly checks for the file type when a file is uploaded. But even though a file type outside of $allowed is uploaded, it will still be shown as uploaded on front-end. Is there an event-handler to prompt the user an error message instead of echoing to console? I have already read the comments above, but using alert("Error") does not create a browser alert view.

  53. José says:

    Great Script! Thanks Martin Angelov!

  54. ivan says:

    Even if file is not uploaded, status is success. Also, when I try to upload file with the wrong file name, status is again ok, but file is not uploaded. This script should be fixed.

  55. SoyUnEmilio says:

    Nice but it only Works for modern browsers!

  56. kiash says:

    Super cool tutorial! Now I'm using jQuery Form plugin to upload files. I will try your way..
    thanks!

  57. Ralf says:

    Hi Martin, this is a real helpful feature thank you very much for publishing it.
    One thing I would like to include: a button or check-mark which removes all entries when upload has succeeded. Or a automatism, that after a certain time after last upload the file names of succeeded uploads disappear. That would help to see the ones much quicker where the upload has led to errors.
    Any ideas how to realize this?
    Thanks, Ralf

  58. randomguy says:

    Martin, is it possible to write small tut for d'n'd and upload files without using jQuery and <form> tag? Thanks in advance

  59. Gaspar Laprida says:

    Hey,

    i can't find my way into accepting only one file! Its easy trought the input field, just erasing the 'multiple' attribute... but my dropZone still accepts multiple files.

    Can you help me?

    And about the 'succes:' method i was asking for, 'done:' method it is.

    Thanks in advance

  60. Giorgio says:

    Really useful tutorial! The first document I've found teaching clearly how to use File Upload library. Thank you very much, Martin!

  61. Justin says:

    Has anybody gotten this to work within Safari? Seems to work great for other browsers based on Android/Windows but not the mac. Any leads? Thanks
    -Justin

  62. Dominic says:

    Hi Martin,

    This is exactly what i was looking for but I would like to send the file to a web service and not php. Where would I change the code to do so?

  63. Nicolas says:

    Hi,

    It looks like there's a glitch when you try to use this script with dojo 1.9.1...
    The weird part is that it works fine with dojo 1.6.1

    The error is ${..}.fileupload is not a function...

    Any idea what could cause that ?

    thnx

  64. Gab says:

    Hi Martin,

    I would just like to ask where should I put javascripts that could read the JSON data printed by the PHP file after a file upload has completed?

    Also, I need to make some adjustments to the handler of the event when the check icon is clicked for each file? The 'li' disappears when it is clicked so I'm assuming it's a delete command. I just need to know as I need to delete the files in the server if they are removed by the user.

    Great tutorials, keep it up and more power.

    Regards,
    Gab

  65. BroLy says:

    Hi,
    I changed: $allowed = array('zip', 'jpg', 'gif','png','rar','txt','jar','doc','docx');
    in upload.php but I still can't upload .rar or .zip, it won't show them in my directory, but .jpg files work perfectly.

  66. Loyiso says:

    Hi Martin, thank you for the awesome tutorial. Just one question, is there a way for me to set the URL of the PHP script within script.js instead of setting it on the form element? My application is built using CakePHP MVC framework so my form has to be submitted to the controller. Any help would be greatly appreciated. Thanks

  67. George Butiri says:

    The fix for Safari browser could be as simple as this:

    #drop input {
    	display: block;
    	opacity: 0;
    }
    

    Let me know please if this does not work for you.

    I've had to deal with this before when coding something similar for iPhone. The reason being that Safari doesn't like hidden elements to be posted as a security risk.

  68. Bogdan says:

    This is really a very good script. But I have a problem in the file name when saving. How can I save a file in UTF-8 encoding

  69. Vinbongun says:

    If you want the name of the file saved in utf-8, then you need to add a line in upload.php.
    $_FILES['upl']['name'] = iconv("utf-8", "windows-1251", $_FILES['upl']['name']);
    after:
    $extension = pathinfo($_FILES['upl']['name'], PATHINFO_EXTENSION);

Add Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [some text](http://example.com) for links