How to read local files

This topic describes how to read local files using the File API.

  • Introduction
  • Selecting files
    • Using the input element to select files
    • Using a drag and drop box to select files
  • Reading files
  • Related topics

Introduction

Internet Explorer 10 (and later) provides the ability to read files in a secure way from the local file system (client machine) without the need for extensions or plugins. This topic covers the following core file-related tasks:

  • Selecting local files using the input element as well as a drag and drop (DnD) box.
  • Reading selected files, displaying their content, and displaying file meta-data such as size, file type, creation date, etc.

On the surface, access to the local file system may seem like a rather large security hole. However, a number of safeguards have been included; particularly the fact that files can only be accessed if the user provides permission to do so.

Note  The following code examples require a browser that supports File API, such as Internet Explorer 10 or later.

 

Selecting files

There are two common ways for a user to select local files. The simplest approach is to use the input element. The other but somewhat more complicated approach is to create a DnD box.

Using the input element to select files

By using type="file" on the input element, it becomes relatively simple to select files:

<input type="file" id="fileSelector" multiple accept="image/*">

When clicking the resulting Browse button, the multiple attribute allows the user to select more than one file in the ensuing file selection dialog. The accept attribute only allows the user to select graphics files such as .jpg, .gif, .bmp, etc. The following simple example shows how to use the input element to select multiple graphics files:

Example 1

<!DOCTYPE html>
<html>
  <head>
    <title>&lt;input&gt; File Selection</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
  </head>
  <body>
    <h1>HTML5 &lt;input&gt; File Selection</h1>
    <h3>Example 1</h3>
    <input type="file" id="fileSelector" multiple accept="image/*" /> <!-- By design, if you select the exact same files two or more times, the 'change' event will not fire. -->
    <ul id="fileContentList" style="list-style-type: none;"></ul> <!-- This will be populated with <li> elements via JavaScript. -->
    <script type="text/javascript">
      var message = [];

      if (!document.getElementById('fileSelector').files) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
      
        document.querySelector('body').innerHTML = message;
      }
      else {
        document.getElementById('fileSelector').addEventListener('change', handleFileSelection, false); // Add an onchange event listener for the <input id="fileSelector"> element.
      }
      
      function handleFileSelection(evt) {    
        var files = evt.target.files; // The files selected by the user (as a FileList object).
  
        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // The variable "files" is an array of file objects. 
        for (var i = 0, file; file = files[i]; i++) {
          var img_element = document.createElement('img'); // We've only allowed the user to select graphics files, so get ready to display them.
          img_element.src = window.URL.createObjectURL(file); // Assumes "file" is some sort of graphics file type.
          img_element.width = 150; // Make all images the same width.
          img_element.style.verticalAlign = "middle"; // Center the image in the middle of adjacent text.
          img_element.style.margin = "4px 4px 4px 0";
          img_element.onload = function() { window.URL.revokeObjectURL(this.src); } // The file URL is not needed once the file image has been fully loaded.
          
          var span_element = document.createElement('span');
          span_element.innerHTML = file.name;
          
          var li_element = document.createElement('li');
          li_element.appendChild(img_element);
          li_element.appendChild(span_element);
          
          document.getElementById('fileContentList').appendChild(li_element);
        } // for
      } // handleFileSelection
    </script>  
    <script src="../utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->
  </body>
</html>

In the above, <meta http-equiv="X-UA-Compatible" content="IE=10"> instructs Windows Internet Explorer to display the page in IE10 mode. For more information, see Defining Document Compatibility.

The script block is placed at the end of the body block to, in general, improve performance but more importantly to allow access to previously rendered DOM elements such as <input type="file" id="fileSelector" multiple accept="image/*" />.

The algorithm for example 1 is simple:

  1. If the specific file API feature is not present, replace all prior markup with HTML that alerts the user that the necessary File API feature is not available. Otherwise, add an event listener (handleFileSelection) to listen for any changes on the input element.

  2. When the user selects one or more files (via the input element), a change event fires which invokes the handleFileSelection function. The event object passed to handleFileSelection contains the list of files selected by the user, namely evt.target.files. Then, for each file object in the list of files, we create a list of image thumbnails and display the associated file name.

Note  Because alert can’t be used in Windows Store apps using JavaScript, we use msa.alert instead. If you're not developing for Windows Store apps using JavaScript, you can replace msa.alert with JavaScript's traditional alert with no loss in functionality.

 

Using a drag and drop box to select files

The prior input element technique for selecting files is relatively simple but may not be as aesthetically pleasing as desired. A more sophisticated approach is to create a file DnD box, as shown in the next example:

Example 2

<!DOCTYPE html>
<html>
  <head>
    <title>Drag &amp; Drop File Selection</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">
    <style>
      #fileDropBox {
        width: 20em;
        line-height: 10em;
        border: 1px dashed gray;
        text-align: center;
        color: gray;
        border-radius: 7px;
    </style>
  </head>
  <body>
    <h1>HTML5 Drag and Drop File Selection</h1>
    <h3>Example 2</h3>
    <p>Using Windows Explorer (or similar), select one or more files (directories are not allowed), and then drag them to the below drop box:</p>
    <div id="fileDropBox">Drop files here.</div>
    <ul id="list"></ul>
    <script>
      var message = [];

      if (!window.FileReader) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
      
        document.querySelector('body').innerHTML = message;
      }
      else {
        // Set up the file drag and drop listeners:         
        document.getElementById('fileDropBox').addEventListener('dragover', handleDragOver, false);
        document.getElementById('fileDropBox').addEventListener('drop', handleFileSelection, false);
      }

      function handleDragOver(evt) {
        evt.stopPropagation();  // Do not allow the dragover event to bubble.
        evt.preventDefault(); // Prevent default dragover event behavior.
      } // handleDragOver()

      function handleFileSelection(evt) {
        evt.stopPropagation(); // Do not allow the drop event to bubble.
        evt.preventDefault(); // Prevent default drop event behavior.

        var files = evt.dataTransfer.files; // Grab the list of files dragged to the drop box.

        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // "files" is a FileList of file objects. List a few file object properties:    
        var output = [];
        for (var i = 0, f; i < files.length; i++) {
          try {
            f = files[i]; // If anything goes awry, the error would occur here.
            output.push('<li><strong>', 
                        f.name, '</strong> (',
                        f.type || 'unknown file type', 
                        ') - ',
                        f.size, ' bytes, last modified: ',
                        f.lastModifiedDate, 
                        '</li>');
            document.getElementById('list').innerHTML = output.join('');
          } // try
          catch (fileError) {
            msa.alert("<p>An unspecified file error occurred.</p><p>Selecting one or more folders will cause a file error.</p>");
            console.log("The following error occurred at i = " + i + ": " + fileError); // Send the error object to the browser's debugger console window, if active.
            return;
          } // catch
        } // for
      } // handleFileSelection()      
    </script>    
    <script src="../utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->        
  </body>
</html>

The file DnD box is simply a CSS styled div element (whose ID is fileDropBox), with two attached event listeners. The first event listener, handleDragOver, is used to nullify the dragover event. The second, handleFileSelection, is used to display file metadata information (file name, size, etc.)

When the handleFileSelection function is invoked, it is passed an event object (evt) that contains the list of file(s) selected by the user:

var files = evt.dataTransfer.files;

Then, for each file in the file list, we build a bulleted list (pushed into the output array) of assorted file metadata information. This bulleted list is then displayed using the <ul id=”list”> element via:

document.getElementById('list').innerHTML = output.join('');

Reading files

Now that we are able to select files, the next step is to display the contents of selected text files. The following example does just that.

Example 3

<!DOCTYPE html>
<html>
  <head>
    <title>Drag &amp; Drop File Selection</title>
    <meta http-equiv="X-UA-Compatible" content="IE=10">    
    <style>
      #fileDropBox {
        width: 20em;
        line-height: 10em;
        border: 1px dashed gray;
        text-align: center;
        color: gray;
        border-radius: 7px;
    </style>
  </head>
  <body>
    <h1>HTML5 Drag and Drop File Selection</h1>
    <h3>Example 3</h3>
    <p>Using Windows Explorer (or similar), select one or more text files (directories are not allowed), and then drag them to the below drop box:</p>
    <div id="fileDropBox">Drop files here.</div>
    <script>
      var message = [];

      if (!window.FileReader) {
        message = '<p>The ' +
                  '<a href="http://dev.w3.org/2006/webapi/FileAPI/" target="_blank">File API</a>s ' +
                  'are not fully supported by this browser.</p>' +
                  '<p>Upgrade your browser to the latest version.</p>';
      
        document.querySelector('body').innerHTML = message;
      }
      else {
        // Set up the file drag and drop listeners:         
        document.getElementById('fileDropBox').addEventListener('dragover', handleDragOver, false);
        document.getElementById('fileDropBox').addEventListener('drop', handleFileSelection, false);
      }
      
      function sanitizeHTML(htmlString) {
        var tmp = document.createElement('div');
        tmp.appendChild( document.createTextNode(htmlString) );
        return tmp.innerHTML;
      } // stripHtmlFromText

      function handleDragOver(evt) {
        evt.stopPropagation();  // Do not allow the dragover event to bubble.
        evt.preventDefault(); // Prevent default dragover event behavior.
      } // handleDragOver

      function displayFileText(evt) {
        var fileString = evt.target.result; // Obtain the file contents, which was read into memory.

        //evt.target is a FileReader object, not a File object; so window.URL.createObject(evt.target) won't work here!
        msa.alert("<pre>" + sanitizeHTML(fileString) + "</pre>", {width: 40, tile: true});  // sanitizeHTML() is used in case the user selects one or more HTML or HTML-like files and the <pre> tag preserves both spaces and line breaks.
      } // displayFileText

      function handleFileReadAbort(evt) {
        msa.alert("File read aborted.");
      } // handleFileReadAbort

      function handleFileReadError(evt) {
        var message;
        
        switch (evt.target.error.name) {
          case "NotFoundError":
            msa.alert("The file could not be found at the time the read was processed.");
            break;
          case "SecurityError":
            message = "<p>A file security error occured. This can be due to:</p>";
            message += "<ul><li>Accessing certain files deemed unsafe for Web applications.</li>";
            message += "<li>Performing too many read calls on file resources.</li>";
            message += "<li>The file has changed on disk since the user selected it.</li></ul>";
            msa.alert(message);
            break;
          case "NotReadableError":
            msa.alert("The file cannot be read. This can occur if the file is open in another application.");
            break;
          case "EncodingError":
            msa.alert("The length of the data URL for the file is too long.");
            break;
          default:
            msa.alert("File error code " + evt.target.error.name);
        } // switch
      } // handleFileReadError

      function startFileRead(fileObject) {
        var reader = new FileReader();

        // Set up asynchronous handlers for file-read-success, file-read-abort, and file-read-errors:
        reader.onloadend = displayFileText; // "onloadend" fires when the file contents have been successfully loaded into memory.
        reader.abort = handleFileReadAbort; // "abort" files on abort.
        reader.onerror = handleFileReadError; // "onerror" fires if something goes awry.

        if (fileObject) { // Safety first.
          reader.readAsText(fileObject); // Asynchronously start a file read thread. Other supported read methods include readAsArrayBuffer() and readAsDataURL().
        }
      } // startFileRead

      function handleFileSelection(evt) {
        evt.stopPropagation(); // Do not allow the drop event to bubble.
        evt.preventDefault(); // Prevent default drop event behavior.

        var files = evt.dataTransfer.files; // Grab the list of files dragged to the drop box.

        if (!files) {
          msa.alert("<p>At least one selected file is invalid - do not select any folders.</p><p>Please reselect and try again.</p>");
          return;
        }

        // "files" is a FileList of file objects. Try to display the contents of each file:
        for (var i = 0, file; file = files[i]; i++) {
          if (!file) {
            msa.alert("Unable to access " + file.name); 
            continue; // Immediately move to the next file object.
          }
          if (file.size == 0) {
            msa.alert("Skipping " + file.name.toUpperCase() + " because it is empty.");
            continue;
          }
          if ( !file.type.match('text/.*') ) {
            msa.alert("Skipping " + file.name.toUpperCase() + " because it is not a known text file type.");
            continue;
          }
          startFileRead(file); // Asychronously fire off a file read request.
        } // for
      } // handleFileSelection      
    </script>
    <script src="utilities.js" type="text/javascript"></script> <!-- Provides the msa.alert() method. -->    
  </body>
</html>

Because example 3 is based on example 2, the algorithm is similar:

  1. If the specific file API feature is not present, replace all prior markup with HTML that alerts the user that the necessary File API feature is not available. Otherwise, add two DnD event listeners to the DnD box:

    <div id="fileDropBox">Drop files here.</div>
    
  2. When a user drags files from a Windows Explorer window (or similar) to the DnD box, the drop event fires and executes the handleFileSelection function. The event (evt) passed to handleFileSelection contains the file(s) the user selected.

  3. handleFileSelection then loops through each file it was handed (via evt.dataTransfer.files), does some basic error checking, and fires off an asynchronous read request for the given file via startFileRead(file). When the file has successfully been read into memory, an event handler is invoked to do something useful with the file data, as discussed next.

  4. The startFileRead(fileObject) function creates a new FileReader object for each file passed to it. This per file FileReader object (i.e., reader in the code) exposes properties intended to contain function pointers (i.e., event handlers). reader also exposes a number of file reading methods, in particular reader.readAsText. When the reasAsText method successfully (and asynchronously) loads the file’s content into memory, the function pointer contained in reader.onloadend is invoked. That is, the displayFileText event handler is executed.

  5. The event object passed to displayFileText contains the results of the read (as instigated by reader.reasAsText). More precisely, evt.target.result contains the results of the requested read, in the form of a string, for the associated text file. We then display this string (that is, the file’s textual contents) as follows:

    msa.alert("<pre>" + sanitizeHTML(fileString) + "</pre>", {width: 40, tile: true});
    

    In case the user selects one or more HTML or HTML-like files, santizeHTML is called to replace "<" with "&lt;", ">" with "&gt;", etc. The <pre> tag is used to preserve the file content’s inherent spaces and line breaks. And as mentioned above, the standard alert method would work just as well as msa.alert but is forbidden in Windows Store apps using JavaScript. The second parameter to msa.alert is an object literal that passes optional parameters to this method. In particular, tile: true allows multiple alert boxes to be tiled (that is, offset slightly) and width: 40 produces an alert box that is 40em units wide.

  6. Moving back to the startFileRead function, if a file read error should occur or if the user cancels a read operation, the handleFileReadError and handleFileReadAbort event handlers, respectively, are invoked.

You should now have the ability to read local files (and the contents thereof) using HTML5 and JavaScript. Try experimenting with some of the other FileReader read methods as well. In particular, try writing your own application using the readAsArrayBuffer method.

Internet Explorer 10 Samples and Tutorials

How to manage local files