AJAX File Upload with Progress Bar using Jquery and PHP

An example of AJAX file upload which will handle multiple files upload with progress bars just like uploadify.js. Learn how to upload multiple files with progress bars in jQuery and PHP.

AJAX File Upload with Progress Bar using Jquery and PHP

AJAX (Asynchronous JavaScript and XML) is a technique which allows you to update a web page without reloading the page. With AJAX you can receive data from server, send data to server and receive response from server after the page has been loaded.

In this post we will learn AJAX file upload with progress bar using jQuery and PHP to handle multiple file uploads via AJAX without reloading the page and maintaining a good user experience. We will also be showing a progress bar for each file upload separately to indicate the upload progress.

We will need to create following files to achieve multiple files upload with AJAX.  

  • index.php: which will contain the form code.
  • style.css: will contain the styles of page.
  • javascript.js: will be holding the code to handle AJAX file upload.
  • process-upload.php: will process the file upload on server side.
 

Create an HTML File Upload Form

First we need to create a form with enctype="multipart/form-data" to allow user to select files and upload them. Then we also add container for each progress bar to displayed in. Since we are going to handle AJAX requests with jQuery we need to add jQuery as well.

index.php

<!DOCTYPE html>
<html>
<head>
<title>AJAX File Upload with Progress Bar using Jquery and PHP - Demo</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link rel="stylesheet" href="css/style.css" />

<script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="js/javascript.js"></script>
</head>
<body>
<div class="container">
<div class="my-4">
<form id="ajax-upload-form" class="ajax-upload-form" enctype="multipart/form-data">
<div class="row">
<div class="col-6">
<input type="file" class="file-input" name="ajax_file" multiple="multiple"/>
</div>
<div class="col-6 text-right">
<button type="button" class="btn btn-blue">
<i class="fa fa-upload"></i>
Upload
</button>
</div>
</div>
</form>
</div>
<div class="progress-container"></div>
</div>
</body>
</html>

AJAX File Upload and Progress Bar

Our javascript.js will handle the AJAX file upload for each uploaded file on client side. We are also showing progress bar for each file that is uploaded. The step by step explanation of code:

  • Listen to form submission event and prevent regular form submission.
  • Create a new form data object for uploaded files.
  • Loop through each selected file from file field  and append progress bar element to container.
  • Check if current file in loop is an image then add the file to form data object.
  • Call the upload_file function providing it the parameters "form data", "counter" and "current file". 

Our javascript file has a function "upload_file(fd, count, file)" which is called for each uploaded file.

  • fd: Form data object which is passed to jQuery AJAX request.
  • count: counter of current file in loop, to show the upload progress of current file.
  • file: The file itself, we need it to show the file name in progress bar.

javascript.js

$(document).ready(function () {
$("#ajax-upload-form").submit(function (e) {
e.preventDefault(); // Prevent form from being submitted.

let fd = new FormData(); // Create new form data object to push files into
let files = $(".file-input")[0].files; // Getting all files from input field

// Loop through all files and append progress bar for each file
for (let i = 0; i < files.length; i++) {
let file = files[i];
let regex = /^(image)\/|^(audio)\/|^(video)\//;
let progressElCount = $("#progress-" + i).length + i;

let progressEl ='<div class="progress" id="progress-' + progressElCount + '">' +
'<span class="abort">&times;</span>' +
'<div class="progress-title"></div>' +
'<div class="progress-bar"></div>' +
'</div>';

$(".progress-container").append(progressEl);// Append progress bar to container

// If file is an image then process upload otherwise add error class to progress element
if (file.type.match(regex)) {
fd.append("ajax_file", file);

// Function to upload single file
upload_file(fd, progressElCount, file);
} else {
$("#progress-" + progressElCount).addClass("progress-error");
$("#progress-" + progressElCount).find(".progress-title")
.text(file.name + " is invalid");
$("#progress-" + progressElCount).find(".progress-bar").css({"width": "100%"});
}
// If all files have been uploaded reset the form
if (i === (files.length - 1))
this.reset();
}
});

// Remove progress bar with error class
$(document).on("click", ".progress-error > .abort", function () {
$(this).closest(".progress").fadeOut(3000, function () {
$(this).remove();
});
});
});

function upload_file(fd, count, file) {
const ajax = $.ajax({
url: "process-upload.php",// Server side script to process uploads
type: "POST",
data: fd,
processData: false, //Bypass jQuery's form data processing
contentType: false, //Bypass jQuery's content type to handle file upload
xhr: function () {
const xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
const progressEl = $("#progress-" + count);

// Listen to upload progress and update progress bar
xhr.upload.addEventListener("progress", function (progress) {
const total = Math.round((progress.loaded / progress.total) * 100);

progressEl.find(".progress-bar").css({"width": total + "%"});
progressEl.find(".progress-title").text(file.name + " - " + total + "%");
}, false);

// Code to be executed if upload is aborted
xhr.addEventListener("abort", function () {
progressEl.fadeOut(3000, function () {
$(this).remove();
});
}, false);

// Update progress and remove it after upload has finished
xhr.addEventListener("loadend", function () {
progressEl.fadeOut(3000, function () {
$(this).remove();
});
}, false);

// Show an error on progress if an error has occurred during upload
xhr.addEventListener("error", function () {
progressEl
.addClass("progress-error").find("status-count").text("Error");
}, false);

// Show timeout error on progress bar if upload request has timed out
xhr.addEventListener("timeout", function () {
progressEl
.addClass("progress-timed-out").find("status-count").text("Timed Out");
}, false);
}
return xhr;
}
});

// Bind abort to current ajax request.
$(document).on("click", "#progress-" + count + " > .abort", function () {
ajax.abort();
});
}
 

Move Uploaded File in PHP

Our process-upload.php file is handling the file upload on server side. Server side upload is:

  • Check if $_FILES global array is not empty and it does not have any error.
  • Check if uploaded file is one of the allowed types.
  • Finally move uploaded file to "uploaded-files" directory.

process-upload.php

<?php
// Code to process uploads
if(!empty($_FILES) && $_FILES['ajax_file']['error'] == 0){
$allowed_types = ['image', 'audio', 'video'];

$type = substr($_FILES['ajax_file']['type'],0,5);

// Check if uploaded file is image,audio or video then save file
if(in_array($type, $allowed_types)){
//move_uploaded_file($_FILES['ajax_file']['tmp_name'], 'uploaded-files/'.$_FILES['ajax_file']['name']);
}
}
 

Add CSS Styles to Page 

Add CSS styles for whole page and progress bars.

style.css

*{
box-sizing: border-box;
}
html,body{
margin: 0px;
padding: 0px;
}
body{
background-color: #f6f6f6;
font-family: "Segoe UI", "Roboto", "Helvetica", sans-serif;
font-size: 15px;
font-weight: normal;
font-style: normal;
line-height: 1.5;
}
.container{
width: 100%;
max-width: 1140px;
margin-right: auto;
margin-left: auto;
padding-right: 15px;
padding-left: 15px;
}
.my-4{
margin-top: 1rem;
margin-bottom: 1rem;
}
.row{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
.col-6{
width: 100%;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
-webkit-box-flex: 0;
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.text-right{
text-align: right;
}
.btn{
display: inline-block;
padding: 5px 10px;
cursor: pointer;
font: inherit;
}
.btn-blue{
background-color: #006699;
border: 1px solid #2b7cab;
color: #ffffff;
}
.progress{
background: #fff;
border: 1px solid #00a65a;
padding: .75rem 1.25rem;
margin-bottom: 1rem;
}
.progress > .abort{
font-size: 1.5em;
line-height: 1;
cursor: pointer;
font-weight: 700;
float: right;
}
.progress-error{
border: 1px solid #e65442;
color: #e65442;
}
.progress > .progress-bar{
background-color: #00a65a;
width: 0;
max-width: 100%;
height: 5px;
margin-top: 0.25rem;
transition: width ease .1s;
}
.progress-error > .progress-bar{
background-color: #e65442;
}