Upload A File With HTML5 (XMLHttpRequest)

Upload A File
XMLHttpRequest object has been updated several times since was defined as the WHATWG’s HTML effort using Microsoft technology; then we had the original XMLHttpRequest Level 1 specification as part of W3C, we also had the XMLHttpRequest Level 2 updated specification, and now we have the last version of this object known as XMLHttpRequest Living Specification. We can summarize its advantages in the following points:

  • Allows upload and download files as stream bytes, large binaries (BLOBs) or data forms
  • It has event handlers for progress, errors, abortion, start, and end of operations
  • Cross-domain capabilities (CORS)
  • New type for JSON responses
  • Is a fundamental part of the HTML5 File API specification

It’s important to emphasize that before HTML5 and the latest versions of XMLHttpRequest object it was required to resort to server-side technology in order to able to perform an uploading file operation, that is, it wasn’t possible to upload a file natively from the client side. Technologies as AJAX and Flash did their own effort but with serious limitations, so XMLHttpRequest comes to cover this old problem in a big way. There are other additional features that come with XMLHttpRequest, if you want to know more you can resort to the official specification.

Starting

The first thing we’ll do is to define the user interface for this small implementation starting with the HTML tags, the code is very simple and only includes some form elements, and some div tags that are only used to give a better presentation using CSS3. I won’t analyze in this post anything related to the used cascade style sheets since is not something really necessary for the operation of this example.

<!DOCTYPE html>
<html>
    <head>
        <title>Upload File</title>
        <meta charset="iso-8859-1" />
    </head>
    <body>
    <div id="wrap">
       <div class="field">
           <ul class="options">
              <li><input type="file" id="myfile" name="myfile" class="rm-input" onchange="selectedFile();"/></li>
              <li><div id="fileSize"></div></li>
              <li><div id="fileType"></div></li>
              <li><input type="button" value="Subir Archivo" onClick="uploadFile()" class="rm-button" /></li>
           </ul>
       </div>
       <progress id="progressBar" value="0" max="100" class="rm-progress"></progress>
       <div id="percentageCalc"></div>
    </div>
    </body>
</html>
The previous code explains itself, but let’s summarize what is in there:
  • A file-type input which will be used to select the file to be uploaded
  • A div which will be used to print the size of the selected file
  • A div which will be used to print the MIME type of the selected file
  • A button which will fire the uploading process for the selected file
  • A progress bar to indicate the uploading process progress of the selected file
  • Finally, a div where the progress will be shown in a percentage format

The selectedFile() function

Each time you select a file using the file element, you also trigger the onchange event which calls the selectedFile() function. In this function very interesting things happen, to start, a reference to a file array instantiated by the HTML5 object FileList is done, the objects that we get as members of FileList are File objects. In this case, we’ll get the size and type properties from the gotten File object.
Using the information provided by the size property, the size of the selected file is calculated and shown in megabytes or kilobytes within the function. With the type property, the MIME type of the selected files is gotten and showed in the corresponding div.
function selectedFile() {
var archivoSeleccionado = document.getElementById("myfile");
var file = archivoSeleccionado.files[0];
   if (file) {
       var fileSize = 0;
       if (file.size > 1048576)
          fileSize = (Math.round(file.size * 100 / 1048576) / 100).toString() + ' MB';
       else
          fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + ' Kb';

       var divfileSize = document.getElementById('fileSize');
       var divfileType = document.getElementById('fileType');
       divfileSize.innerHTML = 'Tamaño: ' + fileSize;
       divfileType.innerHTML = 'Tipo: ' + file.type;

    }
}

The uploadFile() function

Esta es la función que hace un mayor uso de las nuevas posibilidades de XMLHttpRequest , y es la que se encargará de disparar el proceso principal del lado cliente.

function uploadFile(){
 //var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile";
 var url = "/ReadMoveWebSite/UploadMinimal.aspx";
 var archivoSeleccionado = document.getElementById("myfile");
 var file = archivoSeleccionado.files[0];
 var fd = new FormData();
 fd.append("archivo", file);
 var xmlHTTP = new XMLHttpRequest();
 //xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false);
 xmlHTTP.upload.addEventListener("progress", progressFunction, false);
 xmlHTTP.addEventListener("load", transferCompleteFunction, false);
 xmlHTTP.addEventListener("error", uploadFailed, false);
 xmlHTTP.addEventListener("abort", uploadCanceled, false);
 xmlHTTP.open("POST", url, true);
 //xmlHTTP.setRequestHeader('book_id','10');
 xmlHTTP.send(fd);
}
At the beginning, we have the variable url that we’ll use to indicate where is the page o web service which is going to receive the request from this page to do the proper process on server side. Immediately like in the selectedFile() function, a reference to the gotten File object member is also done.
In the fourth line, there is something new and very useful, that is the FormData object, this object allows to instantiate a web form via JavaScript, that is, is like you put an HTML form using tags, or you can refer to an already existing one assigning it to a FormData object. No doubt this is really helpful since means now you can create a web form y alter the sending values dynamically. To append values to, either instantiated or referenced web form with FormData, use the append(file, object) method, this way in the fifth line our File object is added with the file name.
This is the code of the function that covers what was just stated:
//var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile";
var url = "/ReadMoveWebSite/UploadMinimal.aspx";
var archivoSeleccionado = document.getElementById("myfile");
var file = archivoSeleccionado.files[0];
var fd = new FormData();
fd.append("archivo", file);

Event handlers

Continuing with the rest of the function, we can observe that the XMLHttpRequest object is finally instantiated and is assigned to the xmlHTTP variable, and then we proceed to the next novelty, I mean the possibility of creating new events which are part of XMLHttpRequest thanks to the upload object. The added events for this particular case are:
  • loadstart. Is triggered when the uploading file process initiates.
  • progress. Is triggered each time there is an advance in the file uploading process.
  • load. Is triggered when the transfer is complete successfully.
  • error. Is triggered when the transfer fails
  • abort. Is triggered when the user/developer interrupts the process.
These aren’t the only available events, check the official specification for more information.
The event handlers are declared in the following code:
var xmlHTTP= new XMLHttpRequest();
//xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false);
xmlHTTP.upload.addEventListener("progress", progressFunction, false);
xmlHTTP.addEventListener("load", transferCompleteFunction, false);
xmlHTTP.addEventListener("error", uploadFailed, false);
xmlHTTP.addEventListener("abort", uploadCanceled, false);

The triggered events functions are the following:

function progressFunction(evt){
    var progressBar = document.getElementById("progressBar");
    var percentageDiv = document.getElementById("percentageCalc");
    if (evt.lengthComputable) {
        progressBar.max = evt.total;
        progressBar.value = evt.loaded;
        percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";
    }
}
 
function loadStartFunction(evt){
    alert('Comenzando a subir el archivo');
}
 
function transferCompleteFunction(evt){
    alert('Transferencia completa');
    var progressBar = document.getElementById("progressBar");
    var percentageDiv = document.getElementById("percentageCalc");
    progressBar.value = 100;
    percentageDiv.innerHTML = "100%";
}
 
function uploadFailed(evt) {
    alert("Hubo un error al subir el archivo.");
}
 
function uploadCanceled(evt) {
    alert("La operación se canceló o la conexión fue interrunpida.");
}

ProgressFunction() updates the progress bar and percentage which indicate in a graphical and numerical way the process progress, the rest of the functions only display the proper message for each case.

Commented code

If you have observed the code you probably noticed some commented lines, this is because this is just the base code to create something a little bit more complex, but I decided to leave those lines because maybe can be useful for someone:

//var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile";

The previous line of code is a call to a .Net HTTP service instead of a page. Here is where you should call your own server-side implementation.

//xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false);

This line calls a function that shows a message when the process starts. I commented this line after I executed the code several times because was annoying.

The completo code

This is how the complete implementation of the code looks:

<!DOCTYPE html>
<html>
    <head>
        <title>Upload File</title>
        <meta charset="iso-8859-1" />
                                    <link rel="stylesheet" type="text/css" href="estilosUploadFile.css" />
        <script type="text/javascript">
 
              function selectedFile() {
                var archivoSeleccionado = document.getElementById("myfile");
                var file = archivoSeleccionado.files[0];
                if (file) {
                    var fileSize = 0;
                    if (file.size > 1048576)
                        fileSize = (Math.round(file.size * 100 / 1048576) / 100).toString() + ' MB';
                    else
                        fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + ' Kb';
 
                    var divfileSize = document.getElementById('fileSize');
                    var divfileType = document.getElementById('fileType');
                    divfileSize.innerHTML = 'Tamaño: ' + fileSize;
                    divfileType.innerHTML = 'Tipo: ' + file.type;
 
                }
              }     
 
            function uploadFile(){
                //var url = "http://localhost/ReadMoveWebServices/WSUploadFile.asmx?op=UploadFile";
                var url = "/ReadMoveWebServices/WSUploadFile.asmx/UploadFile";
                var archivoSeleccionado = document.getElementById("myfile");
                var file = archivoSeleccionado.files[0];
                var fd = new FormData();
                fd.append("archivo", file);
                var xmlHTTP= new XMLHttpRequest();
                //xmlHTTP.upload.addEventListener("loadstart", loadStartFunction, false);
                xmlHTTP.upload.addEventListener("progress", progressFunction, false);
                xmlHTTP.addEventListener("load", transferCompleteFunction, false);
                xmlHTTP.addEventListener("error", uploadFailed, false);
                xmlHTTP.addEventListener("abort", uploadCanceled, false);
                xmlHTTP.open("POST", url, true);
                //xmlHTTP.setRequestHeader('book_id','10');
                xmlHTTP.send(fd);
            }       
 
            function progressFunction(evt){
                var progressBar = document.getElementById("progressBar");
                var percentageDiv = document.getElementById("percentageCalc");
                if (evt.lengthComputable) {
                    progressBar.max = evt.total;
                    progressBar.value = evt.loaded;
                    percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";
                }
            }
 
            function loadStartFunction(evt){
                alert('Comenzando a subir el archivo');
            }
            function transferCompleteFunction(evt){
                alert('Transferencia completa');
                var progressBar = document.getElementById("progressBar");
                var percentageDiv = document.getElementById("percentageCalc");
                progressBar.value = 100;
                percentageDiv.innerHTML = "100%";
            }   
 
            function uploadFailed(evt) {
                alert("Hubo un error al subir el archivo.");
            }
 
            function uploadCanceled(evt) {
                alert("La operación se canceló o la conexión fue interrunpida.");
            }
 
        </script>
    </head>
    <body>
        <div id="wrap">
            <div class="field">
                <ul class="options">
                    <li>
                        <input type="file" id="myfile" name="myfile" class="rm-input" onchange="selectedFile();"/>
                    </li>
                    <li>
                        <div id="fileSize"></div></li>
                    <li>
                        <div id="fileType"></div></li>
                    <li>
                        <input type="button" value="Subir Archivo" onClick="uploadFile()" class="rm-button" /></li>
                </ul>
            </div>
            <progress id="progressBar" value="0" max="100" class="rm-progress"></progress>
            <div id="percentageCalc"></div>
        </div>
    </body>
</html>

I’m not describing the CSS3 code because is irrelevant in terms of functionality, but I share an image that shows how it looks the implementation in the browser and the link to the CSS3 estilosUploadFile.zip.

I’m also sharing the original HTTP service that I used to test this example -the server-side code, backend file or any name that you prefer 😃- but this won’t be very useful for you unless you use the exact same stack that I was using at the moment- in other words, if you are the kind of person who just wants to copy and paste….hehehe well….maybe you’re not ready for this yet. Here is the file estilosUploadFile.zip

Sorry about my English I’m not a natural speaker (don’t be grumpy, help me to improve).

This is all for now folks, I hope this can be useful for you.

Here some books that can help you in your HTML5 journey:

IT professional with several years of experience in management and systems development with different goals within public and private sectors.

Leave Comment

Your email address will not be published. Required fields are marked *

%d bloggers like this: