Monday, July 9, 2012

Uploading Files with AJAX


I can’t seem to reach the end of the fun stuff you can do with emerging web technologies. Today, I’m going to show you how to do something that—until the last while—has been almost unprecedented: uploading files via AJAX.
Oh, sure, there have been hacks; but if you’re like me, and feel dirty every time you type iframe, you’re going to like this a lot. Join me after the jump!

Why don’t we get the bad news over with?
This doesn’t work in every browser. However, with some progressive enhancement (or whatever the current buzzword is), we’ll have an upload page that will work right back to IE6 (albeit without the AJAXy bits).
Our AJAX upload will work as long as FormData is available; otherwise, the user will get a normal upload.
There are three main components to our project.
  • The multiple attribute on the file input element.
  • The FileReader object from the new File API.
  • The FormData object from XMLHttpRequest2.
We use the multiple attribute to allow the user to select multiple files for upload (multiple file upload will work normally even if FormData isn’t available). As you’ll see, FileReaderallows us to show the user thumbnails of the files they’re uploading (we’ll be expecting images).
None of our three features work in IE9, so all IE users will get a normal upload experience (though I understand support for `FileReader` is available in IE10 Dev Preview 2). FileReaderdoesn’t work in the latest version of Safari (5.1), so they won’t get the thumbnails, but they’ll get the AJAX upload and the confirmation message. Finally, Opera 10.50 has FileReadersupport but not FormData support, so they’ll get thumbnails, but normal uploads.
With that out of the way, let’s get coding!

Step 1: The Markup and Styling

Let’s start with some basic markup and styling. Of course, this isn’t the main part of this tutorial, I won’t treat you like a newbie.

The HTML

  1. >  
  2. <html lang="en">  
  3. <head>  
  4.     <meta charset="UTF-8" />  
  5.     <title>HTML5 File APItitle>  
  6.     <link rel="stylesheet" href="style.css" />  
  7. head>  
  8. <body>  
  9.     <div id="main">  
  10.         <h1>Upload Your Imagesh1>  
  11.         <form method="post" enctype="multipart/form-data"  action="upload.php">  
  12.             <input type="file" name="images" id="images" multiple />  
  13.             <button type="submit" id="btn">Upload Files!button>  
  14.         form>  
  15.   
  16.         <div id="response">div>  
  17.         <ul id="image-list">  
  18.   
  19.         ul>  
  20.     div>  
  21.   
  22.     <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js">script>  
  23.     <script src="upload.js">script>  
  24. body>  
  25. html>  
Pretty basic, eh? We’ve got a form that posts to upload.php, which we’ll look at in a second, and a single input element, of type file. Notice that it has the boolean multiple attribute, which allows the user to select multiple files at once.
That’s really all there is to see here. Let’s move on.

The CSS

  1. body {  
  2.     font14px/1.5 helvetica-neue, helveticaarial, san-serif;  
  3.     padding:10px;  
  4. }  
  5.   
  6. h1 {  
  7.     margin-top:0;  
  8. }  
  9.   
  10. #main {  
  11.     width300px;  
  12.     margin:auto;  
  13.     background#ececec;  
  14.     padding20px;  
  15.     border1px solid #ccc;  
  16. }  
  17.   
  18. #image-list {  
  19.     list-style:none;  
  20.     margin:0;  
  21.     padding:0;  
  22. }  
  23. #image-list li {  
  24.     background#fff;  
  25.     border1px solid #ccc;  
  26.     text-align:center;  
  27.     padding:20px;  
  28.     margin-bottom:19px;  
  29. }  
  30. #image-list li img {  
  31.     width258px;  
  32.     vertical-alignmiddle;  
  33.     border:1px solid #474747;  
  34. }  
Absolutely no shockers here.

Step 2: The PHP

We need to be able to handle the file uploads on the back end as well, so let’s cover that next.

upload.php

  1.   
  2. foreach ($_FILES["images"]["error"as $key => $error) {  
  3.     if ($error == UPLOAD_ERR_OK) {  
  4.         $name = $_FILES["images"]["name"][$key];  
  5.         move_uploaded_file( $_FILES["images"]["tmp_name"][$key], "uploads/" . $_FILES['images']['name'][$key]);  
  6.     }  
  7. }  
  8.   
  9. echo "

    Successfully Uploaded Images

    "
    ;  
Bear in mind that these were the first lines of PHP I’d written in easily a year (I’m a Ruby guy). You should probably be doing a bit more for security; however, we’re simply making sure that there are no upload errors. If that’s the case, we use the built-in move_uploaded_file to move it to an uploads folder. Don’t forget to make sure that the folder is writable.
So, right now, we should have a working upload form. You choose an image (multiple, if you want to and your browser lets you), click the “Upload Files!” button, and you get the message “Successfully Uploaded Images.
Here’s what our mini-project looks like so far:
The styled form
But, come on, it’s 2011: we want more than that. You’ll notice that we’ve linked up jQuery and an upload.jsfile. Let’s crack that open now.

Step 3: The JavaScript

Let’s not waste time: here we go!
  1. (function () {  
  2.     var input = document.getElementById("images"),  
  3.         formdata = false;  
  4.   
  5.     if (window.FormData) {  
  6.         formdata = new FormData();  
  7.         document.getElementById("btn").style.display = "none";  
  8.     }  
  9.   
  10. }();  
Here’s what we start with. We create two variables: input is our file input element; formdata will be used to send the images to the server if the browser supports that. We initialize it to false and then check to see if the browser supports FormData; If it does, we create a new FormData object. Also, if we can submit the images with AJAX, we don’t need the “Upload Images!” button, so we can hide it. Why don’t we need it? Well, we’re going to auto-magically upload the images immediately after the user selects them.
The rest of the JavaScript will go inside your anonymous self-invoking function. We next create a little helper function that will show the images once the browser has them:
  1. function showUploadedItem (source) {  
  2.     var list = document.getElementById("image-list"),  
  3.         li   = document.createElement("li"),  
  4.         img  = document.createElement("img");  
  5.     img.src = source;  
  6.     li.appendChild(img);  
  7.     list.appendChild(li);  
  8. }  
The function takes one parameter: the image source (we’ll see how we get that soon). Then, we simply find the list in our markup and create a list item and image. We set the image source to the source we received, put the image in the list item, and put the list item in the list. Voila!
Next, we have to actually take the images, display them, and upload them. As we’ve said, we’ll do this when the onchange event is fired on the input element.
  1. if (input.addEventListener) {  
  2.     input.addEventListener("change"function (evt) {  
  3.         var i = 0, len = this.files.length, img, reader, file;  
  4.   
  5.         document.getElementById("response").innerHTML = "Uploading . . ."  
  6.   
  7.         for ( ; i < len; i++ ) {  
  8.             file = this.files[i];  
  9.   
  10.             if (!!file.type.match(/image.*/)) {  
  11.   
  12.             }  
  13.         }  
  14.   
  15.     }, false);  
  16. }  
We don’t have to worry about IE’s proprietary event model, because IE9+ supports the standard addEventListener function.
There’s more, but let’s start with this. First off, we don’t have to worry about IE’s proprietary event model, because IE9+ supports the standard addEventListener function (and IE9 and down don’t support our new features).
So, what do we want to do when the user has selected files? First, we create a few variables. The only important one right now is len = this.files.length. The files that the user has selected will be accessible from the object this.files. Right now, we’re only concerned with the length property, so we can loop over the files …
… which is exactly what we’re doing next. Inside our loop, we set the current file to file for ease of access. Next thing we do is confirm that the file is an image. We can do this by comparing the type property with a regular expression. We’re looking for a type that starts with “image” and is followed by anything. (The double-bang in front just converts the result to a boolean.)
So, what do we do if we have an image on our hands?
  1. if ( window.FileReader ) {  
  2.     reader = new FileReader();  
  3.     reader.onloadend = function (e) {  
  4.         showUploadedItem(e.target.result);  
  5.     };  
  6.     reader.readAsDataURL(file);  
  7. }  
  8. if (formdata) {  
  9.     formdata.append("images[]", file);  
  10. }  
We check to see if the browser supports creating FileReader objects. If it does, we’ll create one.
Here’s how we use a FileReader object: We’re going to pass our file object to thereader.readAsDataURL method. This creates a data url for the uploaded image. It doesn’t work the way you might expect, though. The data url isn’t passed back from the function. Instead, the data url will be part of an event object.
With that in mind, we’ll need to register a function on the reader.onloadend event. This function takes an event object, by which we get the data url: it’s at e.target.result (yes, e.target is the reader object, but I had issues when using reader in place of e.target inside this function). We’re just going to pass this data url to our showUploadedItem function (which we wrote above).
Next, we check for the formdata object. Remember, if the browser supports FormDataformdata will be aFormData object; otherwise, it will be false. So, if we have a FormData object, we’re going to call theappend method. The purpose of a FormData object is to hold values that you’re submitting via a form; so, the append method simply takes a key and a value. In our case, our key is images[]; by adding the square-brackets to the end, we make sure each time we append another value, we’re actually appending it to that array, instead of overwriting the image property.
We’re almost done. In our for loop, we’ve displayed each of the images for the user and added them to theformdata object. Now, we just need to upload the images. Outside the for loop, here’s the last piece of our puzzle:
  1. if (formdata) {  
  2.     $.ajax({  
  3.         url: "upload.php",  
  4.         type: "POST",  
  5.         data: formdata,  
  6.         processData: false,  
  7.         contentType: false,  
  8.         success: function (res) {  
  9.             document.getElementById("response").innerHTML = res;  
  10.         }  
  11.     });  
  12. }  
Again, we have to make sure we have FormData support; if we don’t, the “Upload Files!” button will be visible, and that’s how the user will upload the photos. However, if we have FormData support, we’ll take care of uploading via AJAX. We’re using jQuery to handle all the oddities of AJAX across browsers.
You’re probably familiar with jQuery’s $.ajax method: you pass it an options object. The urltype, andsuccess properties should be obvious. The data property is our formdata object. Notice thoseprocessData and contentType properties. According to jQuery’s documentation, processData is trueby default, and will process and transform the data into a query string. We don’t want to do that, so we set this to false. We’re also setting contentType to false to make sure that data gets to the server as we expect it to.
And that’s it. Now, when the user loads the page, they see this:
Tutorial Image
And after they select the images, they’ll see this:
Tutorial Image
And the images have been uploaded:
Tutorial Image

That’s a Wrap!

Uploading files via AJAX is pretty cool, and it’s great that these new technologies support that without the need for lengthy hacks. If you’ve got any questions about what we’ve done here, hit those comments! Thank you so much for reading!