Skip to Content

jQuery, AJAX & PHP Asynchronous Multiple File Upload

The days of uploading files on a web page with no clear indication of whether or not the uploads are working or how long they will take to complete are long gone. We have the technology to improve the process and create a better user experience to show exactly what's happening when it's happening.

This article goes through the step-by-step process of setting up a basic HTML form with the ability to select and upload multiple files with jQuery and PHP on submission. The standard form submission is replaced with a jQuery method that uploads the selected files in chunks via AJAX, then completes the file upload process in the back-end using a PHP script.

The HTML Form

Here is a basic HTML form that contains a multi-select file input field and a submit button:

<form id="upload" encType="multipart/form-data">
<input type="file" name="file[]" multiple="true" />
<button type="submit">Upload</button>
</form>

<div id="alert"></div>

The file input field allows multiple file selections by setting its multiple attribute to true and also by appending [] to the end of the field name, indicating the result will be an array of selected files.

We've also added an alert box that we'll use to display the final results of the jQuery file upload.

The Progress Bar

We'll also create a simple progress bar that contains a border, and inner bar to show progress, and percent completed:

<div id="progress-outer">
<div id="progress-inner"></div>
<div id="progress-text">0%</div>
</div>

Here is an explanation of each of the containers for reference:

  • progress-outer: The outer element of the progress bar.
  • progress-inner: The inner element of the progress bar, showing the actual upload progress made real-time for visual purposes.
  • progress-text: This shows the written percentage in the middle of the progress bar.

Handling the Form Submit Event

There are a few methods and event listeners we're going to create and bring together to make the whole upload process work seamlessly.

First, let's create our form submit event handler:

$("form#upload").submit(function(event) {
event.preventDefault();

$.ajax({
type: "POST",
url: "upload.php",
data: new FormData($(this)[0]),
cache: false,
contentType: false,
processData: false,
xhr: function() {
var my_xhr = $.ajaxSettings.xhr();

if (my_xhr.upload) {
my_xhr.upload.addEventListener("progress", function(event) {
Progress(event.loaded, event.total);
});
}

return my_xhr;
},
success: function() {
$("div#alert").removeClass("red").addClass("green").text("Your file has been uploaded successfully!").show();
},
error: function(xhr, status, message) {
$("div#alert").removeClass("green").addClass("red").text(xhr.status + " " + status + " - " + message).show();
}
});
});

Essentially, we're using jQuery's built-in $.ajax() method to chunk upload our selected files asynchronously. There's a lot here, so let's break down each of the method's parameters individually:

  • type: Set to POST so the browser can submit and process the file uploads with jQuery successfully.
  • url: This is the URL for the PHP script we'll create shortly that will process the uploads server-side in the background.
  • data: Pass the form data directly to the server-side script that will process the files.
  • cache: Set to false to prevent caching issues, ensuring the correct files we've selected are uploaded successfully each time.
  • contentType: Set to false to prevent the browser from expecting a specific file type server-side.
  • processData: This tells jQuery to not format the submitted data as key/value pairs in JSON format. The data will be submitted as objects to be processed in the back-end.
  • xhr: Callback used for creating the XMLHttpRequest object. This is where our real-time file upload data will be received to be displayed on the front-end.
  • success: Callback used when the function has been processed in its entirety successfully. This callback will display the success message to our alert element.
  • error: Callback used when an error from the server-side script has been received. This callback will display the details of the error to our alert element.

The Progress Bar

To display upload progress in real-time, we need to create an event handler to the progress event that returns the current progress and total file upload size.

We've started by creating a new variable, my_xhr, that references the global AJAX XHR methods from jQuery:

var my_xhr = $.ajaxSettings.xhr();

From there, we'll check for the existence of the upload object within our global method. If it exists, we create a progress event that returns the current state of the upload.

if (my_xhr.upload) {
my_xhr.upload.addEventListener("progress", function(event) {
Progress(event.loaded, event.total);
});
}

To dumb it down, we're only looking for two values returned:

  • loaded: The amount, in bytes, that have been successfully uploaded to the server.
  • total: The total amount, in bytes, of all of our selected files that will be uploaded to the server once completed.

These two values will be passed to a custom function we'll create called Progress() which handles the calculations to a percentage value and the display of that value and progress bar animation to the screen:

function Progress(current, total) {
var percent = ((current / total) * 100).toFixed(0) + "%";

$("div#progress-outer").show();
$("div#progress-inner").width(percent);
$("div#progress-text").text(percent);

if (percent == "100%") {
Finished();
}
}

The display is pretty self-explanatory. For the calculation piece, we're basically calculating a percentage value so we can update our inner progress bar and text containers by taking the current value successfully uploaded, in bytes, and dividing it by the total of all file sizes combined, in bytes and converting it to a whole number for easy readability.

Once the file upload progress has reached 100%, we'll call another custom function that we'll create, Finished().

Finishing the Front-End Upload Process

In our custom Finished() method, we're going to change the progress text to something a bit more readable while clearing out the selected files from the form's input field, allowing for the selection of new files easily.

We're also going to wait three seconds after so the completed message can be displayed for long enough that it won't confuse the user and so they know their files actually uploaded successfully:

function Finished() {
setTimeout(function() {
$("form#upload input[type='file']").val("");
$("div#progress-text").text("Upload Complete");

setTimeout(function() {
$("form#upload input[type='file']").val("");
$("div#progress-outer").hide();
$("div#progress-inner").width(0);
$("div#progress-text").text("0%");
$("div#alert").hide();
}, 3000);
}, 500);
}

You can really handle this process any way you want. I just thought this would be the most user-friendly approach and also demonstrates the flexibility you have with your forms and progress bar display.

Using PHP for Asynchronous File Uploads

The front-end functionality is great, and also a lot of fun to play around with, but we'll need a way to process the actual file uploads server-side so we can access them later in our application as needed.

First, we'll get the file count:

$file_count = sizeof($_FILES["file"]["name"]);

This will be used to loop through each of the uploaded files one file at a time, which we'll do here:

for ($i = 0; $i < $file_count; $i++) {
$file_name = $_FILES["file"]["name"][$i];
$location = "upload/" . $file_name;

if (move_uploaded_file($_FILES["file"]["tmp_name"][$i], $location)) {
header("HTTP/1.1 200 OK");
} else {
header("HTTP/1.1 500 Internal Server Error");
}
}

The syntax for accessing data on a single file is a bit odd, in my opinion, but it gets the job done. Rather than looping through each file, then accessing its data through a single array, each of its data attributes has an array. As you can see, the name attribute has its own array of data, as well as the tmp_name for each file.

We're basically looping through each of the files by accessing its temporary file name that was given when uploading the file, tmp_name, and renaming it to its actual name on upload completion using the name attribute of the file object.

PHP's move_uploaded_file() method handles this by moving the file from its temporary upload location to the directory of our choosing on the server.

If all files are uploaded successfully, we return a successful result. Otherwise, we'll return an internal server error indicating that there was an issue during the upload process.

Again, you can handle this any way you would like. I just wanted to quickly show an example that made sense without causing too much confusion. These scripts can be modified to function or display as you need in your own applications.

The Progress Bar CSS

Last, but not least, let's not forget the CSS for the progress bar.  This CSS code creates the other and inner portions of the progress bar, as well as styling the progress text and creating an animation for the inner bar:

div#progress-outer {
width: 315px;
height: 30px;
border: 1px solid white;
border-radius: 30px 30px 30px 30px;
-moz-border-radius: 30px;
-webkit-border-radius: 30px;
padding: 5px;
position: relative;
margin-top: 30px;
overflow: hidden;
display: none;
}

div#progress-inner {
width: 0%;
height: 100%;
background-color: white;
border-radius: 30px 30px 30px 30px;
-moz-border-radius: 30px;
-webkit-border-radius: 30px;
transition: 0.25s all ease-in-out;
-moz-transition: 0.25s all ease-in-out;
-webkit-transition: 0.25s all ease-in-out;
}

div#progress-text {
width: 100%;
font-family: Arial, Helvetica, serif;
font-weight: bold;
font-size: 18px;
color: green;
text-align: center;
position: absolute;
top: 10px;
left: 0px;
z-index: 2;
}

Conclusion

Accomplishing asynchronous multiple file uploads with jQuery and PHP is a great way to provide a positive upload experience for your users.

You can access the full code of this example at my GitHub account. And, as always, feel free to leave a comment if you have any notes, questions, or just want to give a shout out!

Last Updated: November 24, 2021
Created: August 07, 2021

Comments

There are no comments yet. Start the conversation!

Add A Comment

Comment Etiquette: Wrap code in a <code> and </code>. Please keep comments on-topic, do not post spam, keep the conversation constructive, and be nice to each other.