jQuery

jQuery File Upload Example and Demo

When you develop an application, most, if not all, of the times, you will have to provide some way for your users to upload files, for example to update their profile picture or to share a document.

In this example, we will see how we can implement this using jQuery.
 
 
 
 
 
 
 
 

1. Introduction

So imagine you have to develop a file sharing tool and you want to support user registration and a functionality where users can upload a profile picture for their accounts. For simplicity, we are going to focus only on those two aspects that matter for this example: uploading a profile picture and uploading any kind of file for sharing.

2. Tools we are going to use

We will be using the following tools:

  1. PHP 5.5
  2. Sublime text 3
  3. Slim framework
  4. Composer
  5. Node.js v0.10.33
  6. Bower
  7. GNU/Linux distro (Ubuntu)

TOC

3. Back end (PHP)

Let’s start with the back end of our application. I am using Slim, a PHP micro framework, to get the job done easily but you can implement it just anyway you want.

First we need to create a composer project and install Slim, so open a terminal or command console and type:

composer init
composer require slim/slim

Composer will ask you a few questions and then it will generate a composer.json file. With composer require slim/slim we are installing Slim as a dependency, you should get an output like the following on your terminal.

Using version ~2.6 for slim/slim
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing slim/slim (2.6.2)
    Downloading: 100%

Writing lock file
Generating autoload files

Your composer.json file should look like:

compose.json

{
    "name": "webcodegeeks/jquery-file-upload",
    "description": "A jQuery file upload example",
    "license": "MIT",
    "authors": [
        {
            "name": "Eivar Montenegro"
        }
    ],
    "require": {
        "slim/slim": "~2.6"
    }
}

We are ready to create our index.php file and start working in the back end.

Create a new file, name it index.php and add the following code to it:

index.php

<?php
require 'vendor/autoload.php';

$app = new \Slim\Slim();

$app->get('/profile/:userId', function ($userId) {
	echo 'Profile picture form will be shown here';
});

$app->post('/profile/:userId', function ($userId) {
	echo 'Profile picture upload will be processed here!';
});

$app->get('/file-upload', function () {
	echo 'File upload form will be shown here';
});

$app->post('/file-upload', function () {
	echo 'File upload will be processed here!';
});

$app->run();

Our index file is starting to have some structure, the simplest way to test our app is using PHP Built-id web server, just run the following command in your terminal php -S localhost:8080 and visit http://localhost:8080/profile/1 in your web browser, there should be a message displayed for you.

TOC

3.1 Profile picture

We can now start working on our profile picture upload; create a new file and name it profile_pic_form.php and add the following code to it:

profile_pic_form.php

<html>
	<head></head>
	<body>
		<?php if (!empty($app->errors)): ?>
			<div id="errors">
				<?php
				foreach ($app->errors as $key => $error) :
					echo "$key: " . var_export($error, true);
				endforeach
				?>
			</div>
		<?php endif ?>
		<?php if (!empty($app->message)): ?>
			<div id="notices">
				<?php
				echo "File {$app->message['name']} uploaded successfully!";
				?>
			</div>
		<?php endif ?>
		<form method="POST" action="/profile/<?php echo $userId ?>" enctype="multipart/form-data" >
			<input type="file" name="profile_pic" value=""/>
			<input type="submit" value="Upload profile picture"/>
		</form>
	</body>
</html>

Here we have defined a simple html form, there are also two conditions to show errors or a message if such variables are not empty, that is just to provide some feedback to the user.

At this point you should be able to select a file from your computer and upload it to our testing back end just remember to start the PHP built-in server using php -S .... Your files would be stored in uploads folder.

We are going to use codeguy/upload a third party library to handle file uploads, let’s add it just like we did with Slim:

composer require codeguy/upload
Using version ~1.3 for codeguy/upload
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing codeguy/upload (1.3.2)
    Downloading: 100%

Writing lock file
Generating autoload files

And we can update our index.php to use new file upload library.

Tip
Make sure that your php.ini file is configured correctly, specially upload_max_filesize

index.php

<?php
require 'vendor/autoload.php';

$appPath = dirname(__FILE__) ;

$app = new \Slim\Slim();
$app->storage = new \Upload\Storage\FileSystem($appPath . '/uploads');

$app->get('/profile/:userId', function ($userId) {
	include_once 'profile_pic_form.php';
});

$app->post('/profile/:userId', function ($userId) use($app) {
	$file = new \Upload\File('profile_pic', $app->storage);
	$newFilename = uniqid();
	$file->setName($newFilename);

	// Validate file
	// MimeType List => http://www.webmaster-toolkit.com/mime-types.shtml
	$file->addValidations([
		new \Upload\Validation\Mimetype(['image/png', 'image/jpeg']),
		new \Upload\Validation\Size('5M'),
	]);
	try {
		$app->message = [
			'name' => $file->getNameWithExtension(),
			'mime' => $file->getMimetype(),
			'size' => $file->getSize(),
			'md5'  => $file->getMd5(),
			'dimensions' => $file->getDimensions()
		];
		$file->upload();
	} catch (\Exception $e) {
		$app->errors = $file->getErrors();
	}
	include_once 'profile_pic_form.php';
});

$app->get('/file-upload', function () {
	echo 'File upload form will be shown here';
});

$app->post('/file-upload', function () {
	echo 'File upload will be processed here!';
});

$app->run();

We have implemented the basic process to upload a file, the most important part here is the post route /profile/:userId because there we create an \Upload\File instance, we add a MIME type validation and store the uploaded file.

Tip
For more details on codeguy/upload you can check the github.com project

TOC

3.2 File share

Now lets do the same for our file sharing routes, first the upload form.

Create the file file_share_form.php, open it and add the following source code:

file_share_form.php

<html>
	<head></head>
	<body>
		<?php if (!empty($app->errors)): ?>
			<div id="errors">
				<?php
				foreach ($app->errors as $key => $error) :
					echo "$key: " . var_export($error, true);
				endforeach
				?>
			</div>
		<?php endif ?>
		<?php if (!empty($app->messages)): ?>
			<div id="notices">
				<?php
				foreach ($app->messages as $message) :
					echo "<p>File {$message['name']} uploaded successfully!<br/></p>";
				endforeach
				?>
			</div>
		<?php endif ?>
		<form method="POST" action="/file-upload" enctype="multipart/form-data" >
			<input type="file" name="a_file" value=""/>
			<input type="submit" value="Upload file"/>
		</form>
	</body>
</html>

Our file sharing form is very similar to the one we used for a profile picture, it’s ok for now we only want to make sure that we can upload a document.

Open your index.php file and add the file upload processing code for /file-upload route:

index.php

<?php
require 'vendor/autoload.php';

$appPath = dirname(__FILE__) ;

$app = new \Slim\Slim();
$app->storage = new \Upload\Storage\FileSystem($appPath . '/uploads');

$app->get('/profile/:userId', function ($userId) {
	include_once 'profile_pic_form.php';
});

$app->post('/profile/:userId', function ($userId) use($app) {
	$file = new \Upload\File('profile_pic', $app->storage);
	$newFilename = uniqid();
	$file->setName($newFilename);

	// Validate file
	// MimeType List => http://www.webmaster-toolkit.com/mime-types.shtml
	$file->addValidations([
		new \Upload\Validation\Mimetype(['image/png', 'image/jpeg']),
		new \Upload\Validation\Size('5M'),
	]);
	try {
		$app->message = [
			'name' => $file->getNameWithExtension(),
			'mime' => $file->getMimetype(),
			'size' => $file->getSize(),
			'md5'  => $file->getMd5(),
			'dimensions' => $file->getDimensions()
		];
		$file->upload();
	} catch (\Exception $e) {
		$app->errors = $file->getErrors();
	}
	include_once 'profile_pic_form.php';
});

$app->get('/file-upload', function () {
	include_once 'file_share_form.php';
});

$app->post('/file-upload', function () use($app) {
	$messages = [];
	foreach ($_FILES as $key => $data) {
		$file = new \Upload\File($key, $app->storage);
		$file->setName(uniqid());
		// Validate file
		// MimeType List => http://www.webmaster-toolkit.com/mime-types.shtml
		$file->addValidations([
			new \Upload\Validation\Mimetype(['image/png', 'image/jpeg', 'application/pdf']),
			new \Upload\Validation\Size('5M'),
		]);
		try {
			array_push($message, [
				'name' => $file->getNameWithExtension(),
				'mime' => $file->getMimetype(),
				'size' => $file->getSize(),
				'md5'  => $file->getMd5(),
				'dimensions' => $file->getDimensions()
			]);
			$file->upload();
		} catch (\Exception $e) {
			$app->errors = $file->getErrors();
			break;
		}
	}
	$app->messages = $messages;
	include_once 'file_share_form.php';
});

$app->run();

Now, when you post a file or files to /file-upload, each one of them will be saved and details of each file will be stored on $app->message.

TOC

4. Front end (jQuery + Dropper)

It is time to improve our front end using jQuery. I am using bower to download all required javascript libraries.

Tip
Bower is a package manager for the web, is optimized for the front-end and keep track of downloaded packages in a manifest file named: bower.json

To install bower you must install node.js first, then use npm to install bower like this: npm install -g bower. Once bower is installed in your system you can use it like bower install <package>

Now we will install Dropper, jquery and bootstrap. Dropper is a jQuery plugin for simple drag and drop uploads.

You can install Dropper using the following command on your terminal: bower install Dropper, this should produce an output similar to:

bower not-cached    git://github.com/benplum/Dropper.git#*
bower resolve       git://github.com/benplum/Dropper.git#*
bower download      https://github.com/benplum/Dropper/archive/1.0.1.tar.gz
bower extract       Dropper#* archive.tar.gz
bower resolved      git://github.com/benplum/Dropper.git#1.0.1
bower install       Dropper#1.0.1

Dropper#1.0.1 bower_components/Dropper

Also install jquery bower install jquery and bootstrap bower install bootstrap and make sure that your dependencies get saved to bower.json executing bower init. Answer the questions presented by bower and you will get a bower.json file similar to:

{
  "name": "jquery-file-upload",
  "version": "0.0.0",
  "authors": [
    "Eivar Montenegro"
  ],
  "main": "index.php",
  "moduleType": [
    "node"
  ],
  "license": "MIT",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "Dropper": "~1.0.1",
    "jquery": "~2.1.3",
    "bootstrap": "~3.3.4"
  }
}

TOC

4.1 Profile picture

We have to include the resources installed by bower, you can find those files in bower_components folder. Let’s modify profile_pic_form.php:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>jQuery file upload example</title>
	<link rel="stylesheet" type="text/css" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
	<link rel="stylesheet" type="text/css" href="/bower_components/Dropper/jquery.fs.dropper.min.css"/>
</head>
<body>
	<div class="container">
		<div class="row">
			<div id="freedback" class="col-md-12">
			</div>
		</div>
		<form method="POST" action="/profile/<?php echo $userId ?>" enctype="multipart/form-data" >
			<div class="form-group">
				<div id="dropzone" class="dropper dropzone">
				</div>
			</div>
		</form>
	</div>
	<script type="text/javascript" src="/bower_components/jquery/dist/jquery.min.js"></script>
	<script type="text/javascript" src="/bower_components/Dropper/jquery.fs.dropper.min.js"></script>
	<script type="text/javascript" src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
	<script type="text/javascript">
		(function ($) {
			$(function() {
				$('#dropzone').dropper({
					action: '/profile/<?php echo $userId ?>',
					postKey: 'profile_pic',
					maxSize: 5242880, //5M
					maxQueue: 10
				})
				.on('start.dropper', function (e, files) {

					$('#freedback').empty().append('<div class="alert alert-info">Upload stared</div>');
				})
				.on('fileStart.dropper', function (e, file) {
					$('#freedback').append('<div class="alert alert-info">Upload stared [' + file.name + '] ' + '0%</div>');
				})
				.on('fileProgress.dropper', function (e, file, percent) {
					$('#freedback').append('<div class="alert alert-info">Upload progress [' + file.name + '] ' + percent +'%</div>');
				})
				.on('fileComplete.dropper', function (e, file, response) {
					$('#freedback').append('<div class="alert alert-info">Upload completed [' + file.name + '] <br/>' + 'server response: ' + response +'</div>');
					if (response.errors) {
						$.each(response.errors, function (index, error) {
							$('#freedback').append('<div class="alert alert-danger">Server error: [' + error+ '] <br/></div>');
						});
					}
					if (response.message) {
						//$.each(response.message, function (index, message) {
							$('#freedback').append('<div class="alert alert-success">Upload success: [' + response.message.name + '] <br/></div>');
						//});

					}
				})
				.on('fileError.dropper', function (e, file, error) {
					$('#freedback').append('<div class="alert alert-danger">' + file.name + ': ' + error + '</div>');
				})
				.on('complete.dropper', function (e) {
					$('#freedback').append('<div class="alert alert-success">All done!</div>');
				});
			});
		})(jQuery);
	</script>
</body>
</html>

Few things have changed, we no longer reload the entire page, so only the results of the upload process are returned in json format. We have several functions that are called in response to Dropper events, the old form was replaced with a drop zone and finally I have added all the bootstrap boilerplate code.

TOC

4.2 File share

Now we gotta repeat the process with file_share_form.php:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>jQuery file upload example</title>
	<link rel="stylesheet" type="text/css" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
	<link rel="stylesheet" type="text/css" href="/bower_components/Dropper/jquery.fs.dropper.min.css"/>
</head>
<body>
	<div class="container">
		<div class="row">
			<div id="freedback" class="col-md-12">
			</div>
		</div>
		<form method="POST" action="/file-upload" enctype="multipart/form-data" >
			<div class="form-group">
				<div id="dropzone" class="dropper dropzone">
				</div>
			</div>
		</form>
	</div>
	<script type="text/javascript" src="/bower_components/jquery/dist/jquery.min.js"></script>
	<script type="text/javascript" src="/bower_components/Dropper/jquery.fs.dropper.min.js"></script>
	<script type="text/javascript" src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
	<script type="text/javascript">
		(function ($) {
			$(function() {
				$('#dropzone').dropper({
					action: '/file-upload',
					postKey: 'file',
					maxSize: 5242880, //5M
					maxQueue: 10
				})
				.on('start.dropper', function (e, files) {
					$('#freedback').empty().append('<div class="alert alert-info">Upload stared</div>');
				})
				.on('fileStart.dropper', function (e, file) {
					$('#freedback').append('<div class="alert alert-info">Upload stared [' + file.name + '] ' + '0%</div>');
				})
				.on('fileProgress.dropper', function (e, file, percent) {
					$('#freedback').append('<div class="alert alert-info">Upload progress [' + file.name + '] ' + percent +'%</div>');
				})
				.on('fileComplete.dropper', function (e, file, response) {
					$('#freedback').append('<div class="alert alert-info">Upload completed [' + file.name + '] <br/>' + 'server response: ' + response +'</div>');
					if (response.errors) {
						$.each(response.errors, function (index, error) {
							$('#freedback').append('<div class="alert alert-danger">Server error: [' + error+ '] <br/></div>');
						});
					}
					if (response.message) {
						$.each(response.message, function (index, message) {
							$('#freedback').append('<div class="alert alert-success">Upload success: [' + message.name + '] <br/></div>');
						});

					}
				})
				.on('fileError.dropper', function (e, file, error) {
					$('#freedback').append('<div class="alert alert-danger">' + file.name + ': ' + error + '</div>');
				})
				.on('complete.dropper', function (e) {
					$('#freedback').append('<div class="alert alert-success">All done!</div>');
				});
			});
		})(jQuery);
	</script>
</body>
</html>

Just like before we have some functions for handling all the events triggered by Dropper, we replaced the from with a drop zone and all the bootstrap boilerplate was added.

Last but not least, we need to update index.php so it will send the correct format (JSON). Here is the final index.php file:

<?php
require 'vendor/autoload.php';

$appPath = dirname(__FILE__) ;

$app = new \Slim\Slim();
$app->storage = new \Upload\Storage\FileSystem($appPath . '/uploads');

$app->get('/profile/:userId', function ($userId) {
	include_once 'profile_pic_form.php';
});

$app->post('/profile/:userId', function ($userId) use($app) {
	$app->response->headers->set('Content-Type', 'application/json');
	$file = new \Upload\File('profile_pic', $app->storage);
	$newFilename = uniqid();
	$file->setName($newFilename);

	// Validate file
	// MimeType List => http://www.webmaster-toolkit.com/mime-types.shtml
	$file->addValidations([
		new \Upload\Validation\Mimetype(['image/png', 'image/jpeg']),
		new \Upload\Validation\Size('5M'),
	]);
	$response = ['message' => null, 'errors' => null];
	try {
		$response['message'] = [
			'name' => $file->getNameWithExtension(),
			'mime' => $file->getMimetype(),
			'size' => $file->getSize(),
			'md5'  => $file->getMd5(),
			'dimensions' => $file->getDimensions()
		];
		$file->upload();
		$app->response->setStatus(201);
	} catch (\Exception $e) {
		$response['errors'] = $file->getErrors();
		$response['message'] = null;
		$app->response->setStatus(200);
	}
	echo json_encode($response);
});

$app->get('/file-upload', function () {
	include_once 'file_share_form.php';
});

$app->post('/file-upload', function () use($app) {
	$app->response->headers->set('Content-Type', 'application/json');
	$response = ['message' => [], 'errors' => null];
	foreach ($_FILES as $key => $data) {
		$file = new \Upload\File($key, $app->storage);
		$file->setName(uniqid());
		// Validate file
		// MimeType List => http://www.webmaster-toolkit.com/mime-types.shtml
		$file->addValidations([
			new \Upload\Validation\Mimetype(['image/png', 'image/jpeg', 'application/pdf']),
			new \Upload\Validation\Size('5M'),
		]);
		try {
			array_push($response['message'], [
				'name' => $file->getNameWithExtension(),
				'mime' => $file->getMimetype(),
				'size' => $file->getSize(),
				'md5'  => $file->getMd5(),
				'dimensions' => $file->getDimensions()
			]);
			$file->upload();
			$app->response->setStatus(201);
		} catch (\Exception $e) {
			$response['errors'] = $file->getErrors();
			$app->response->setStatus(200);
			break;
		}
	}
	echo json_encode($response);
});

$app->run();

TOC

5. Download

Download
You can download the full source code of this example here: jQuery File Upload Example

TOC

Eivar Montenegro

Eivar has graduated from the Faculty of Computer Systems Engineering from the technological University of Panama. During his career he has been involved in numerous projects ranging from programming and design of information systems, billing platforms, social networks, mobile applications and web applications. He works as an independent software consultant and is mainly involved with projects based on web technologies like Wordpress, Laravel, Servlets (Java), HTML5, CSS3 and Javascript.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Majid
Majid
9 years ago

Hi,
Thanks for this tutorial, I downloaded the source code but it looks like it is missing the folder : vendor.

Thanks.

Back to top button