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!
Comments
There are no comments yet. Start the conversation!