Top

Follow me and receive all the latest free scripts:

By Email:

Categories
Most Popular Posts

Drag and Drop Multiple Files Upload with HTML5, jQuery & FormData

Drag and Drop Multiple Files Upload with HTML5, jQuery & FormData

Published November 06, 2014 by , category HTML

Drag & DropMultiple Files UploadHTML5jQueryFormDataXMLHttpRequest

HTML5 Drag and Drop Multiple File Uploader using jQuery and FormData

Introduction

In this tutorial I will show you how to build a drag and drop multiple images upload using HTML5 and jQuery. This tutorial follow the "Ajax upload multiple images using PHP and jQuery". You can easily mix both to allow your visitors to upload files using a drag and drop zone or the Browse Server button.

What do I use?

Bootstrap

Bootstrap default progress bar. If you want to use this progress bar you need to install Bootstrap CSS Framework. You may choose to use your own system or a simple waiting animated icon.

jQuery

The famous jQuery JavaScript Library which greatly simplifies JavaScript programming.

How do I proceed?

With XHR2, file upload through AJAX is supported. For example through FormData object, but be careful, it is not supported by all browsers, so also plan a standard File Upload with a Form.

FormData support starts from following desktop browsers versions:

Logo Internet Explorer Logo FireFox Logo Chrome Logo Safari Logo Opera
IE >= 10 Firefox >= 4.0 Chrome >= 7 Safari >= 5 Opera >= 12

How to send FormData objects with Ajax-requests in jQuery?

What is FormData?

The FormData object lets you compile a set of key/value pairs to send using XMLHttpRequest. Its primarily intended for use in sending form data, but can be used independently from forms in order to transmit keyed data. The transmitted data is in the same format that the form's submit() method would use to send the data if the form's encoding type were set to "multipart/form-data".

How to use FormData?

In this tutorial I use FormData with jQuery.

You can instantiate the FormData and then append fields to it by calling its append() method, like this:

var fd = new FormData();
fd.append('file', files[0]);
...

Live Demo

Max: 128 Kb/image; jpg, jpeg, png & gif

0%
Drag & Drop Photos Here

Files architecture

index.php
db.class.php

ajax

tuto-dd-upload-image.php

jquery

js

tuto-dd-upload-image.js

upload

There is an "upload" folder. It's where images and thumbnails will be stored.

Database structure sample

id is the "id" (database row number) of the image stored. Notice that I store the date and time when the image has been uploaded (not required of course), the original image and it's thumbnail.

CREATE TABLE IF NOT EXISTS `tc_tuto_upload_image` (
  `id` int(6) NOT NULL AUTO_INCREMENT,
  `date_ins` date NOT NULL DEFAULT '0000-00-00',
  `hour_ins` time NOT NULL DEFAULT '00:00:00',
  `img_thumb` varchar(255) NOT NULL,
  `img_original` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 ;

Database connection

Page db.class.php. More information about the database connection I use here.

HTML code

This is the index.php page (or any page you want the script appears), I put the full page structure. JavaScript is at the end, before </body> markup, with a link to jquery/jquery-1.10.1.min.js and a link to js/tuto-dd-upload-image.js.

Notice that I put the CSS style between <head></head>. You can add it in an external CSS file...

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
	<title>Drag & Drop Multiple Files Upload</title>
	
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	
	<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" type="text/css">
	<style>
	.dock {
		border: 4px dotted #cccccc;
		background-color: #ededed;
		width: 600px;
		height: 300px;
		color: #aaa;
		font-size: 18px;
		text-align: center;
		padding-top: 100px;
	}
	.dock_hover {
		border: 4px dotted #4d90fe;
		background-color: #e7f0ff;
		width: 600px;
		height: 300px;
		color: #4d90fe;
		font-size: 18px;
		text-align: center;
		padding-top: 100px;
	}
	</style>
</head>

<body>

<div class="container">
	<div class="row">
		<div class="col-md-9">
		
			<div class="progress">
				<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">0%</div>
			</div>

			<div id="result"></div>
			
			<div align="center">
			<div id="dock" class="dock">Drag & Drop Photos Here</div>
			</div>
			
		</div>
	</div>
</div>

<script type="text/javascript" src="jquery/jquery-1.10.1.min.js"></script>
<script type="text/javascript" src="js/tuto-dd-upload-image.js"></script>

</body>
</html>

JS code

It's the tuto-dd-upload-image.js file. Lines are commented.

jQuery code is triggered when user drops files. Files are sent via an Ajax request to ajax/tuto-dd-upload-image.php.

Here is what happens in chronological order:

  1. User drags files over the zone ➜ change the color of the zone,
  2. If user leaves the zone ➜ the zone comes back to its original color,
  3. User drops the files ➜ progress bar at 0% ➜ test the files length ➜ launch the upload function,
  4. Upload function is launched with files argument ➜ create a new FormData object with one append() method per file dropped ➜ one append() method for the number of files dropped,
  5. Send FormData() to server using jQuery AJAX API ➜ calling ajax/tuto-dd-upload-image.php URL.
$(document).ready(function() {
	
	// Add eventhandlers for dragover and prevent the default actions for this event
	$('#dock').on('dragover', function(e) {
		$(this).attr('class', 'dock_hover'); // If drag over the window, we change the class of the #dock div by "dock_hover"
		e.preventDefault();
		e.stopPropagation();
	});
	
	// Add eventhandlers for dragenter and prevent the default actions for this event
	$('#dock').on('dragenter', function(e) {
		e.preventDefault();
		e.stopPropagation();
	});
	
	$('#dock').on('dragleave', function(e) {
		$(this).attr('class', 'dock'); // If drag OUT the window, we change the class of the #dock div by "dock" (the original one)
	});
	
	// When drop the images
	$('#dock').on('drop', function(e){ // drop-handler event
		if (e.originalEvent.dataTransfer) {
			$('.progress-bar').attr('style', 'width: 0%').attr('aria-valuenow', '0').text('0%'); // Bootstrap progress bar at 0%
			if (e.originalEvent.dataTransfer.files.length) { // Check if we have files
				e.preventDefault();
				e.stopPropagation();
				// Launch the upload function
				upload(e.originalEvent.dataTransfer.files); // Access the dropped files with e.originalEvent.dataTransfer.files
			}
		}
	});
	
	function upload(files){ // upload function
		var fd = new FormData(); // Create a FormData object
		for (var i = 0; i < files.length; i++) { // Loop all files
			fd.append('file_' + i, files[i]); // Create an append() method, one for each file dropped
		}
		fd.append('nbr_files', i); // The last append is the number of files
		
		$.ajax({ // JQuery Ajax
			type: 'POST',
			url: 'ajax/tuto-dd-upload-image.php', // URL to the PHP file which will insert new value in the database
			data: fd, // We send the data string
			processData: false,
			contentType: false,
			success: function(data) {
				$('#result').html(data); // Display images thumbnail as result
				$('#dock').attr('class', 'dock'); // #dock div with the "dock" class
				$('.progress-bar').attr('style', 'width: 100%').attr('aria-valuenow', '100').text('100%'); // Progress bar at 100% when finish
			},
			xhrFields: { //
				onprogress: function (e) {
					if (e.lengthComputable) {
						var pourc = e.loaded / e.total * 100;
						$('.progress-bar').attr('style', 'width: ' + pourc + '%').attr('aria-valuenow', pourc).text(pourc + '%');
					}
				}
			},
		});
	}
	
});

PHP code

It's the ajax/tuto-dd-upload-image.php file, including a crop_img function, so for each image uploaded, I store its original size and a thumbnail (160x120 px or 120x160 px depending if it's a landscape or a portrait).

I also rename the original image name and its thumbnail as follow:

Lines are commented. You can easily adapt the script and remove what you don't need.

<?php
include('../db.class.php');
$bdd = new db();

$acceptedExtension = Array('image/jpeg', 'image/jpg', 'image/pjpg', 'image/pjpeg', 'image/png', 'image/gif'); // add here allowed extensions
$maxSize = 5000000; //image size max = 5Mb
$destFolder = 'upload/';

echo '<div class="row">'; // Bootstrap CSS Thumbnails
for($i = 0; $i < $_POST['nbr_files']; $i++) { // Loop through each file
	
	$imgType = $_FILES["file_".$i]["type"];
	$imgSize = $_FILES["file_".$i]["size"];
	$imgName = $_FILES["file_".$i]["name"];
	$imgTmpName = $_FILES["file_".$i]["tmp_name"];

	if (in_array($imgType, $acceptedExtension) && $imgSize <= $maxSize && $imgSize != "") { // we test the validity of the image

		$randNbr = rand(1000000, 9999999); // Choose a random number between 1000000 and 9999999
		$newOriginalImageName = 'img-'.$randNbr.'.'.pathinfo($imgName, PATHINFO_EXTENSION); // Create a new file name including the random number, starting by img-
		$newThumbImageName = 'img-small-'.$randNbr.'.'.pathinfo($imgName, PATHINFO_EXTENSION); // Create a thumbnail name including the random number, starting by img-small-

		if(move_uploaded_file($imgTmpName,"../".$destFolder.$newOriginalImageName)) { // test if the original image is moved on the server
			
			copy("../".$destFolder.$newOriginalImageName, "../".$destFolder.$newThumbImageName); // we copy the origininal image and rename it (it will be our thumbnail)
			chmod ("../".$destFolder.$newThumbImageName, 0777); // we change the chmod so we can crop
					
			// we crop the photo
			list($width, $height, $type, $attr) = getimagesize("../".$destFolder.$newOriginalImageName); // we take the image height and width
			// the crop function is below
			if ($width>$height) { // if the image is landscape style
				crop_img ("../".$destFolder.$newThumbImageName, 160, 120); // we crop and resize 160x120px
			} else { // if the image is portrait style
				crop_img ("../".$destFolder.$newThumbImageName, 120, 160);// we crop and resize 120x160px
			}
			
			// we instert into database; the thumbnail path and the original path
			$upload = $bdd->execute('INSERT INTO tc_tuto_upload_image (date_ins, hour_ins, img_thumb, img_original) VALUES (NOW(), NOW(), "'.$newThumbImageName.'", "'.$newOriginalImageName.'")');
			
			echo '<div class="col-xs-6 col-md-3"><a href="#" class="thumbnail"><img src="'.$destFolder.$newThumbImageName.'" alt="" /></a></div>'; // we send back the thumbnail - check Bootstrap for the CSS
		}

	} else {
		echo '<div class="col-xs-6 col-md-3"><a href="#" class="thumbnail">Error with your image (wrong format or size)!</a></div>';
	}

}
echo '</div>';

// crop function (feel free to adapt)
function crop_img ($image, $thumb_width, $thumb_height) {
	$filename = $image;
	$image = imagecreatefromstring(file_get_contents("$image"));

	$width = imagesx($image);
	$height = imagesy($image);

	$original_aspect = $width / $height;
	$thumb_aspect = $thumb_width / $thumb_height;

	if ( $original_aspect >= $thumb_aspect ) {
	   // If image is wider than thumbnail (in aspect ratio sense)
	   $new_height = $thumb_height;
	   $new_width = $width / ($height / $thumb_height);
	} else {
	   // If the thumbnail is wider than the image
	   $new_width = $thumb_width;
	   $new_height = $height / ($width / $thumb_width);
	}

	$thumb = imagecreatetruecolor($thumb_width, $thumb_height);

	// Resize and crop
	imagecopyresampled($thumb,
		$image,
		0 - ($new_width - $thumb_width) / 2, // Center the image horizontally
		0 - ($new_height - $thumb_height) / 2, // Center the image vertically
		0, 0,
		$new_width, $new_height,
		$width, $height);
	
	return imagejpeg($thumb, $filename, 80);
}
?>

Conclusion

Here we are, end of this tutorial, I hope it will help you .

Don't forget that drag'n'drop requires a HTML5 browser. That's pretty much all of them now, but not old versions.

You may also be interested by these two tutorials:

If you have questions and need help, please comment below.

Happy sharing, happy coding!

About Simon Laroche
Simon Laroche on Google+
Simon Laroche on Twitter
Simon Laroche on Facebook
Simon Laroche on Pinterest
Simon Laroche on LinkedIn
: I am a Coder, Designer, Webmaster and Expert SEO Consulting, I'm also a wise traveller and an avid amateur photographer. I created the website TipoCode and many others such as Landolia: a World of Photos...

If you need help about this script, please leave a comment below. I reply as much as I can depending of my time, you may also get help from others.
I also offer a paid support, if you are in the need to adapt or create a script...

Leave a comment

Comments (5 comments)

Simon Laroche
Simon Laroche Posted on January 21, 2015
@Mac
I have a JS error: "expected expression, got '<'
I think it comes from file tuto-dd-upload-images.js
where you added a variable $pk
Then you try to pass this variable here
ajax/tuto-dd-upload-image.php?pk=<?php echo $pk;?>


And of course it doesn't work.
But I think your problem is the same than Mr DaNNY below, and I gave him the way to pass an extra-argument in the ajax/tuto-dd-upload-image.php file.

Please have a look and tell me if it resolves your problem.
Mac Posted on January 21, 2015

Its working fine on local server but when i put this online, it doesn't work at all.

Please have a look at this http://192.185.169.217/~ebichar/demo/multi/

Quick response will be highly appreciated.

Thanks.
DaNNY Posted on January 08, 2015
Thanks Simon.. all works fine....(and thanks for the step by step comments, it really helps someone with slow brain :) )
Simon Laroche
Simon Laroche Posted on January 08, 2015
@DaNNY
Thx for your interest in my script.
Here is the solution:

1) index.php file
Replace
<div id="dock" class="dock">Drag & Drop Photos Here</div>

By
<div id="dock" class="dock" rel='{"pid":<?php echo $pid; ?>}'>Drag & Drop Photos Here</div>


You add a rel tank with a JSON including your page PID, you can pass many other parameters :)

2) tuto-dd-upload-image.js file
Two modifications in this file
2.1)
Replace

// When drop the images
$('#dock').on('drop', function(e){
if (e.originalEvent.dataTransfer) {
$('.progress-bar').attr('style', 'width: 0%').attr('aria-valuenow', '0').text('0%');
if (e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
// Launch the upload function
upload(e.originalEvent.dataTransfer.files);
}
}
});

By

$('#dock').on('drop', function(e){

var relData = $.parseJSON($(e.target).attr('rel'));

if (e.originalEvent.dataTransfer) {
$('.progress-bar').attr('style', 'width: 0%').attr('aria-valuenow', '0').text('0%'); // Bootstrap progress bar at 0%
if (e.originalEvent.dataTransfer.files.length) {
e.preventDefault();
e.stopPropagation();
// Launch the upload function
upload(e.originalEvent.dataTransfer.files, relData.pid);
}
}
});


So in fact you add the line:
var relData = $.parseJSON($(e.target).attr('rel'));

To get the "rel" attribute parameters

And you add the parameter relData.pid (your pid) to the upload function (to pass the pid parameter)

2.2)
Add the pid parameter to the function function upload(files){
So you will have
function upload(files, pid){

Inside this function:
Add fd.append('pid', pid);
After fd.append('nbr_files', i);
So you will have these 2 lines:

...
fd.append('nbr_files', i);
fd.append('pid', pid);
...


3) tuto-dd-upload-image.php file
There you get your PID with $_POST['pid']

For example you add the PID on the file name like that
$newOriginalImageName = 'img-'.$_POST['pid'].'-'.$randNbr.'.'.pathinfo($imgName, PATHINFO_EXTENSION);


Hope it helps and solve your problem :)
DaNNY Posted on January 07, 2015
HI, great script (and with good comments throughout), however im trying to modify a little and getting a bit confused....

On the index page, i receive a dynamic variable ($pid = $_GET['id'];).

With this $pid i would like to add this value to each image in the database.

How do i pass this variable using the formdata through to echoing the value in the final php page?

Thanks in advance