In the previous post, we examined the history and the technical details of HTTP-based file uploads using web browsers. In this post, we explore two modern file upload methods: AJAX and Web Sockets.
Asynchronous JavaScript and XML (AJAX) is powered by a technology built into web browsers named the XMLHttpRequest (XHR) object. The XHR object was a Microsoft invention used to support their Outlook Web Access product in 2000. This object was first available as an ActiveX object in Internet Explorer 5.
As the usefulness of XHR became apparent, it was adopted in other web browsers such as Firefox and Safari. However, those browsers did not support ActiveX (a proprietary Microsoft COM-based technology). Therefore, to use the XHR object, developers had to write specific code for each browser in order to perform AJAX calls.
Before the wide-spread adoption of the XHR object, AJAX-like calls were accomplished using hacks such as hidden iframes. With the advent of libraries, such as jQuery and Prototype, common interfaces were created for developers to perform AJAX calls. The libraries then handled the details for each specific browser implementation.
The AJAX capabilities provided the XHR object fueled the Web 2.0 revolution and greatly increased the usefulness of JavaScript-enabled web sites. Nevertheless, as useful as the XHR object was, it lacked one major feature: it could not handle file uploads. To perform asynchronous file uploads, the old school hidden iframe hack with programmatic form submission was needed. The following code sample was adapted from Viral Patel’s blog post titled “Ajax Style File Uploading using Hidden iframe“.
For Hidden iframes (and the XHR uploads in the next section), a standard HTML form is used to display an HTML file input field. This field will be used to specify which file is to be uploaded. In modern browsers, it’s possible to specify multiple files through the file selection dialog box. To select multiple files, add the multiple attribute to the file input element.
<form> <div> <label for="my-file">Select File:</label> <input id="my-file" name="my-file" type="file" multiple> </div> <button type="button" id="upload-button">Upload</button> </form>
Once the file(s) are selected and the upload button is clicked, the iframe needs to be created and the form needs to be submitted.
function createUploadFrame(iframeName) { // create iframe element with specified name, and return it var iframe = document.createElement("iframe"); iframe.setAttribute("id", iframeName); iframe.setAttribute("name", iframeName); iframe.setAttribute("width", 0); iframe.setAttribute("height", 0); iframe.setAttribute("border", 0); iframe.setAttribute("style", "border:0px"); return iframe; }
Using the createUploadFrame function, a new hidden iframe is created. The form will be submitted to this iframe by setting the target attribute on the upload form. To handle the response from the server, an event handler will be registered with the load event of the iframe. This load event will run once the response from the server has been received.
function doFileUpload(uploadFormElement, formActionUrl, uploadCallbackFn) { var uploadFrameName = "iframe-upload", uploadFrame = createUploadFrame(uploadFrameName); // add the created hidden iframe to the DOM uploadFormElement.parentNode.appendChild(uploadFrame); function uploadResult() { // call the supplied callback function with the result of the // upload request uploadCallbackFn(uploadFrame.contentDocument.body.innerHTML); // clean up the load event listener so it does not run again uploadFrame.removeEventListener("load", uploadResult); uploadFrame.parentNode.removeChild(uploadFrame); }; // when the page reloads from the form submission // run the passed in callback function uploadFrame.addEventListener("load", uploadResult); // submit the visible form to the hidden iframe by // submitting the hidden iframe, the main page is not // reloaded, and the submission is asynchronous uploadFormElement.setAttribute("target", uploadFrameName); uploadFormElement.setAttribute("action", formActionUrl); uploadFormElement.setAttribute("enctype", "multipart/form-data"); uploadFormElement.setAttribute("method", "POST"); uploadFormElement.submit(); }
A click of the upload button triggers the upload process. The code below demonstrates the implementation of the click event.
window.addEventListener("DOMContentLoaded", function() { var buttonElement = document.getElementById("upload-button"); buttonElement.addEventListener("click", function() { // performs the file upload // 1. Create a hidden iframe // 2. Post the visible form to the hidden iframe // 3. Call the callback with the response text from the upload request doFileUpload(document.getElementsByTagName("form")[0], "/upload", function(result) { console.log(result); }); }); });
In 2008, work began on the XHR Level 2 specification. Enhancements to the XHR object included progress events, cross-site requests and byte streams. In December 2011, XHR Level 1 and Level 2 were combined into a single specification. The handling of byte streams and progress events greatly enhance the process of performing asynchronous file uploads.
To perform file uploads with the XHR object, the FormData API is used. The FormData API allows the creation of forms programmatically in JavaScript. These forms are not forms displayed on the screen, instead they are in-memory forms which can be populated with many kinds of form data including files to be uploaded.
Because JavaScript in a web browser is highly sandboxed for security reasons, JavaScript cannot directly access the file system. In order for JavaScript to know which file to upload, it must access that file indirectly. In this case, the file to be uploaded will be accessed through the user selection of the file using an on-screen form. Once selected, JavaScript will access the on-screen form input file control to retrieve the file(s) selected by the user. Those files will then be added to the in-memory form data object and passed to the XHR object through the send function.
// Create a new FormData object to hold the file datavar fd = new FormData(); // Add the data from the file input field to the FormData object fd.append("my-file", document.getElementById("my-file").files[0]); // Initialize a new XHR object var xhr = new XMLHttpRequest(); // handle ready state change events xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { // upload is complete, output result data console.dir(JSON.parse(xhr.responseText)); } }; // configure the request xhr.open("POST", "/upload"); // makes the request and uploads the form data xhr.send(fd);
Once the file upload is complete, the response from the server is handled the usual way.
Dragging and Dropping has been a mainstay of desktop applications ever since the first graphical user interface environments 30+ years ago. With the advent of the web and vendor-specific browser capabilities, drag and drop capabilities within browsers have been possible. However, since there were no official standards, drag and drop functionality was rarely implemented in web applications. HTML5 standardized the Drag & Drop API for modern browsers. Through the API, users can drag and drop elements within the window, as well as from outside the browser window. When files from outside the browser are dropped in that window, JavaScript code can then be used to upload those files.
To get started with Drag & Drop, the drop zone needs to be specified. The id value does NOT have to be “drop-zone”, the developer merely has to identify where users are going to be allowed to drop files for uploading.
<form> <div id="drop-zone"></div> </form>
Next, a few styles should be specified to help indicate to the user where they can drop the file, as well as when they have activated the drop zone by dragging a file(s) over it.
<style> #drop-zone { width:200px; height:200px; border: 2px dashed gray; } #drop-zone.active { border: 2px dashed red; } </style>
For drag and drop file upload to work, several events need to be set up. First, the event dragenter is used “activate” the drop zone with a style to indicate to the user that they have dragged a file over the drop zone. Secondly, the dragleave event is needed to “deactivate” the drop zone should the user leave the drop zone without dropping the file. Thirdly, the dragover event is used to prevent the default browser behavior when a file is dragged over the area of the web page.
var dropZone = document.getElementById("drop-zone"); dropZone.addEventListener("dragenter", function(e) { this.classList.add("active"); }); dropZone.addEventListener("dragleave", function(e) { this.classList.remove("active"); }); dropZone.addEventListener("dragover", function(e) { e.preventDefault(); });
Finally, the drop event is needed. The drop event will handle the actual dropping of the file, process the upload, and deactivate the drop zone. To perform the file upload, the drop event will use the same FormData object demonstrated in the earlier example, except instead of referencing a form input file field, it will refer to the file to be uploaded through the dataTransfer property of the event object.
On the dataTransfer property, there is a files list property that provides access to the files that have been dropped on the drop zone. These files are then appended to the instantiated FormData object and uploaded as previously shown with the XHR object.
dropZone.addEventListener("drop", function(e) { e.preventDefault(); this.classList.remove("active"); // create a new FormData object var fd = new FormData(); // iterate over the files dragged on to the browser for (var x=0; x < e.dataTransfer.files.length; x++) { // add each file to FormData object fd.append("file-" + x, e.dataTransfer.files[x]); } var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.dir(JSON.parse(xhr.responseText)); } }; xhr.open("POST", "/upload"); xhr.send(fd); });
HTML5 provides a new element named progress which can be used to display a progress bar. The bar has two attributes, max and value. The progress bar assumes a minimum value of 0, and allows the developer to specify the maximum value. For the purposes of handling file uploads, the maximum value specified with the max attributed is the size in bytes of the file to be uploaded. The value represents the current progress toward the maximum value. For file uploads, the value is the number of bytes that have been uploaded.
<form> <div> <label for="my-file">Select File:</label> <input id="my-file" name="my-file" type="file"> </div> <div> <progress id="upload-progress" class="hide-me"></progress> </div> <button type="button" id="upload-button">Upload Me!</button> </form>
The most recent version of the XHR object provides an upload property. The upload property provides a progress event which is called multiple times during the upload process. Typically, it reports the size of the file being uploaded along with the number of bytes that have been uploaded. Using JavaScript, the code can hook into this event to retrieve the file upload progress in bytes, then use the information to update the progress bar element.
xhr.upload.onprogress = function(e) { // if the file upload length is known, then show progress bar if (e.lengthComputable) { uploadProgress.classList.remove("hide-me"); // total number of bytes being uploaded uploadProgress.setAttribute("max", e.total); // total number of bytes that have been uploaded uploadProgress.setAttribute("value", e.loaded); } };
The code above can simply be inserted into the same code block that wires up the XHR file upload, as seen in previous examples. Full code sample link are provided at the bottom of the page.
The ability to perform file uploads using asynchronous techniques is nothing new, but the ability to use the XHR object, drag & drop, and even track progress, can replace and enhance the old-school approach of using Hidden iframes. HTML5 and enhancements to the XHR object have improved the usability of file uploads while simplifying the implementation details for the developer. In the next post, we will be look at using Web Sockets and Socket.IO to perform file uploads over web sockets using the FileReader and ArrayBuffer APIs available in the web browser. Truly, the web browsers of today are a feature and API rich environment for performing all kinds of useful web tasks.
Web Page File Uploads: History and Future (Part 1)
Web Page File Uploads: Web Sockets (Part 3)
Author: Eric Greene, one of Accelebrate’s instructors.
Accelebrate offers private AngularJS training and JavaScript training for groups and instructor-led online JavaScript classes for individuals.
Written by Eric Greene
Eric is a professional software developer specializing in HTML, CSS, and JavaScript technologies. He has been developing software and delivering training classes for nearly 19 years. He holds the MCSD Certification for ASP.Net Web Applications, and is a Microsoft Certified Trainer.
Our live, instructor-led lectures are far more effective than pre-recorded classes
If your team is not 100% satisfied with your training, we do what's necessary to make it right
Whether you are at home or in the office, we make learning interactive and engaging
We accept check, ACH/EFT, major credit cards, and most purchase orders
Alabama
Birmingham
Huntsville
Montgomery
Alaska
Anchorage
Arizona
Phoenix
Tucson
Arkansas
Fayetteville
Little Rock
California
Los Angeles
Oakland
Orange County
Sacramento
San Diego
San Francisco
San Jose
Colorado
Boulder
Colorado Springs
Denver
Connecticut
Hartford
DC
Washington
Florida
Fort Lauderdale
Jacksonville
Miami
Orlando
Tampa
Georgia
Atlanta
Augusta
Savannah
Hawaii
Honolulu
Idaho
Boise
Illinois
Chicago
Indiana
Indianapolis
Iowa
Cedar Rapids
Des Moines
Kansas
Wichita
Kentucky
Lexington
Louisville
Louisiana
New Orleans
Maine
Portland
Maryland
Annapolis
Baltimore
Frederick
Hagerstown
Massachusetts
Boston
Cambridge
Springfield
Michigan
Ann Arbor
Detroit
Grand Rapids
Minnesota
Minneapolis
Saint Paul
Mississippi
Jackson
Missouri
Kansas City
St. Louis
Nebraska
Lincoln
Omaha
Nevada
Las Vegas
Reno
New Jersey
Princeton
New Mexico
Albuquerque
New York
Albany
Buffalo
New York City
White Plains
North Carolina
Charlotte
Durham
Raleigh
Ohio
Akron
Canton
Cincinnati
Cleveland
Columbus
Dayton
Oklahoma
Oklahoma City
Tulsa
Oregon
Portland
Pennsylvania
Philadelphia
Pittsburgh
Rhode Island
Providence
South Carolina
Charleston
Columbia
Greenville
Tennessee
Knoxville
Memphis
Nashville
Texas
Austin
Dallas
El Paso
Houston
San Antonio
Utah
Salt Lake City
Virginia
Alexandria
Arlington
Norfolk
Richmond
Washington
Seattle
Tacoma
West Virginia
Charleston
Wisconsin
Madison
Milwaukee
Alberta
Calgary
Edmonton
British Columbia
Vancouver
Manitoba
Winnipeg
Nova Scotia
Halifax
Ontario
Ottawa
Toronto
Quebec
Montreal
Puerto Rico
San Juan