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:
<!DOCTYPE html>
<html>
<head>
<title><input> File Selection</title>
<meta http-equiv="X-UA-Compatible" content="IE=10">
</head>
<body>
<h1>HTML5 <input> 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:
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 theinput
element.When the user selects one or more files (via the
input
element), achange
event fires which invokes thehandleFileSelection
function. The event object passed tohandleFileSelection
contains the list of files selected by the user, namelyevt.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:
<!DOCTYPE html>
<html>
<head>
<title>Drag & 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.
<!DOCTYPE html>
<html>
<head>
<title>Drag & 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:
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>
When a user drags files from a Windows Explorer window (or similar) to the DnD box, the
drop
event fires and executes thehandleFileSelection
function. The event (evt
) passed tohandleFileSelection
contains the file(s) the user selected.handleFileSelection
then loops through each file it was handed (viaevt.dataTransfer.files
), does some basic error checking, and fires off an asynchronous read request for the given file viastartFileRead(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.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 particularreader.readAsText
. When thereasAsText
method successfully (and asynchronously) loads the file’s content into memory, the function pointer contained inreader.onloadend
is invoked. That is, thedisplayFileText
event handler is executed.The event object passed to
displayFileText
contains the results of the read (as instigated byreader.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 "<", ">" with ">", etc. The<pre>
tag is used to preserve the file content’s inherent spaces and line breaks. And as mentioned above, the standardalert
method would work just as well asmsa.alert
but is forbidden in Windows Store apps using JavaScript. The second parameter tomsa.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) andwidth: 40
produces an alert box that is 40em units wide.Moving back to the
startFileRead
function, if a file read error should occur or if the user cancels a read operation, thehandleFileReadError
andhandleFileReadAbort
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.