AJAX File Upload with Progress Bar using jQuery and PHP

An AJAX file upload is a technique of uploading files to server after the page has been completely loaded. Learn how to upload multiple files with progress bars with jQuery and PHP.

AJAX File Upload with Progress Bar using jQuery and PHP

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

 

How to Implement an AJAX File Upload with Progress Bars

To implement an AJAX file upload into a web application we first prevent the form submission. Then add all files to a form data object in a client-side script and upload them to the server with an AJAX request. In this post, we will add an AJAX file upload with a progress bar with jQuery and PHP, similar to uploadify.js. This integration will support multiple file uploads and a progress bar for each file upload to indicate the upload progress to the user.

We will need to create the following files to implement this feature.

  • index.html: This file is the user interface containing the HTML form code.
  • style.css: A stylesheet that contains the CSS of the page.
  • javascript.js: A JavaScript to upload files with an AJAX request.
  • process-upload.php: A server-side PHP script to process and save the uploaded files.

Step 1: Create an HTML File Upload Form

The first step is to create an HTML form that has the enctype="multipart/form-data" attribute for multiple files upload support. Then we also add a container for each progress bar to be displayed. Since we are going to handle AJAX file upload requests with jQuery, we need to add jQuery as well.

index.html

<!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"/>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
<link rel="stylesheet" href="css/style.css" />

<script src="js/jquery-3.1.1.min.js" type="text/javascript"></script>
<script src="js/javascript.js" type="text/javascript"></script>
</head>
<body>
<section class="section py-4">
<div class="container">
<form id="ajax-upload-form" class="ajax-upload-form mb-4" 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="submit" class="btn btn-blue">
<i class="fa fa-upload"></i>
Upload
</button>
</div>
</div>
</form>

<div class="progress-container"></div>
</div>
</section>
</body>
</html>

Step 2: jQuery AJAX File Upload and Progress Bar

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

  • Listen to the form submission event and prevent regular form submission.
  • Create a new form data object to add uploaded files to it.
  • Run a loop for every file in the file field and append a progress bar element to the container.
  • Check if the current file in the loop is an image, then add the file to the form data object.
  • Upload the files with our upload_file() function. This function accepts three parameters: "form data", "counter" and "current file".
  • Track file upload progress with xhr.upload in the upload_file function.

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

  • fd: Form data object that is passed to the jQuery AJAX request.
  • count: Counter of the current file in the loop to show the upload progress.
  • file: The file itself, we need it to show the file name in the 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 a 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 a 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 the current AJAX request.
$(document).on("click", "#progress-" + count + " > .abort", function () {
ajax.abort();
});
}
 

Step 3: Handle File Upload on Server Side (PHP)

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

  • Check if the $_FILES array is not empty and does not have any errors.
  • Check the file type of the uploaded file if it is one of the allowed file types.
  • Finally, save the uploaded file using the move_uploaded_file() function. We are saving the files to the uploaded-files directory in our example.

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']);
}
}
 

Step 4: Add File Upload CSS Styles

Add CSS styles for the entire HTML 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;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.mb-4 {
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;
}

We now have a functional AJAX file upload feature that supports multiple file uploads with progress bars for each file. The server-side code in this post can be further extended to add validation and file size limits.