Read local files with the File API

In HTML5, you can edit content from the local system in the browser. John Allsopp investigates.

Read local files with the File API

Learn how to use the local file system to create apps that work better offline

John Allsop will be discussing the future of the web and how you can perpare for it in his talk, Predict the future with this one weird old trick, at Generate Sydney on 5 September. Book now!

As the applications we build with web technologies aspire to match up to native apps, one area where the browser remains challenged is in its access to files on the user's system. Browsers have, for many years, allowed users to select files to upload to a server in HTML form with <input type="file">.

But the content of these files has always been hidden from any JavaScript running in the browser. If we wanted to play video files, or edit images that were on the user's local system, we needed to first upload the file to a server.

If we could read the contents of these files, we could rely less on server-side functionality. Thankfully, with HTML5 we can now read files from the local file system. This means we can create apps that work better offline.

John Allsopp presented 'The web's future is offline' at beyond tellerrand Berlin 2014. Click through to watch the video

John Allsopp presented 'The web's future is offline' at beyond tellerrand Berlin 2014. Click through to watch the video

The HTML input element of type="file" allows users to select one or more files from the local file system. Before HTML5, the purpose of the file input was solely to enable users to select files to be uploaded via a form. In HTML5, the input elements of type="file" now give developers access to metadata about the files selected: the file name, last modification date, and file size in bytes.

If we want the user to be able to choose multiple files at a time, we need to give the input a multiple attribute. Then, we'll create a change event handler for our file input (we'll give this the ID fileChooser), called when the value of the file input changes.

document.querySelector("#fileChooser").addEventListener
('change',filesChosen, false)

When the user chooses a file with the file input, our event handler is called and receives the event as an argument. The target attribute of the event is the file input. This has an attribute file, which is an array like object FileList, containing the file objects selected by the user. Even if we haven't set a multiple attribute on our input, the files attribute is still a FileList, just containing a single item.

Getting data

Now we can iterate over the FileList, working through each selected file in turn and getting its name, size and last modified date:

function filesChosen(evt){
var chosenFile = evt.target.files[0]; //get the first file in the FileList
var fileName = chosenFile.name; //the name of the file as a string
var fileSize = chosenFile.size; //the size of the file in bytes, as an integer
var fileModifiedDate = chosenFile.lastModifiedDate; //a Date object
}

How might we use these? Well, we could use localStorage to remember details about files that have been uploaded to a server, and alert users when we have already uploaded a file with the same name, size and modification date, saving them an upload. Or, before uploading, we might determine those files that may take a long time to upload due to their size, and warn the user.

However, this information is still quite limited. Luckily, HTML5 also provides a way of reading the content of a file.

Generate Sydney - John Allsop

John Allsop will be discussing the future of the web at Generate Sydney; don't miss it!

Reading files

The FileReader object allows us to read the content of a file once we've got a file from the user. For security, the user must actively give us the reference to the file in order for it to be readable. There are a number of FileReader methods for getting a file's contents:

  • readAsDataURL(file): Returns the contents of the file as a dataURL, which might be used as the src of an img element, or an image in a style sheet
  • readAsText(file [,encoding]): Reads the file as a string, with the given optional encoding (default UTF-8)
  • readAsArrayBuffer(file): Reads the contents of a file as an ArrayBuffer

Because JavaScript is single-threaded, and files may potentially be large, the FileReader read methods are asynchronous. This also means multiple files can be read simultaneously. We can stop the reading of a file while it's in progress using FileReader.abort(). Because these methods are asynchronous, rather than setting the value of a variable to the result of the method, we need to listen for events that are sent to the FileReader object.

This article by Alex Feyerke was one of the first to explore the trend for offline-first web applications Click through to read it

This article by Alex Feyerke was one of the first to explore the trend for offline-first web applications Click through to read it

One of the events the FileReader receives is loaded when the file we want has been read. The target property of this event is the FileReader itself – this has a property called result, where the contents of the file that was read is contained. Here's how we would get the dataURL for the contents of a file:

var reader = new FileReader();
//create our FileReader object to read the file

reader.addEventListener("load", fileRead, false);
//add an event listener for the onloaded event

function fileRead(event){
//called when the load event fires on the FileReader

var pictureURL = event.target.result;
//the target of the event is the FileReader object instance
//the result property of the FileReader contains the file contents
}

reader.readAsDataURL(file);
//when the file is loaded, fileRead will be called

We add the event listener for the load event, and in the handler for this event we get the target.result, which is the value of the FileReader operation (so, a DataURL, string or arrayBuffer, depending on what operation we asked the FileReader to perform).

We can listen for any of the following events on the FileReader object:

  • loadstart: When FileReader starts reading the file
  • progress: Intermittently while the file is loading
  • abort: When the file reading is aborted
  • load: When reading completes successfully
  • loadend: When the file has either been loaded or the read has failed

If a read succeeds, both a load and loadend event are fired. If it fails, error and loadend events are fired.

Putting it together

We'll let the user select an image file, then show it as a thumbnail. First, our input element:

<input type="file" id="chooseThumbnail" accept="image/*">
and we add an event listener for when the input changes
document.querySelector("#chooseThumbnail").addEvent
Listener('change',showThumbNail, false)

When the user makes a selection, we call the function showThumbNail(). In HTML5 we can use accept='image/*' to accept any image type. The target of the event is the input element, which has a property file (an array-like object called a FileList). We get the first element in the FileList and read it.

function showThumbNail(evt){
var url;
var file;
file = evt.target.files[0];
reader = new FileReader();
//we need to instantiate a new FileReader object
reader.addEventListener("load", readThumbNail, false);
//we add an event listener for when a file is loaded by the FileReader
//this will call our function 'readThumbNail()'

reader.readAsDataURL(file);
//we now read the data
}
function readThumbNail(event) {
//this is our callback for when the load event is sent to the FileReader
var thumbnail = document.querySelector("#thumbnail");

thumbnail.src = event.target.result;
//the event has a target property, the FileReader with a property 'result',
//which is where the value we read is located
}

URLs for Files

Often, when we access a local file, we won't actually want to use its contents directly, but use the file for some other purpose. For example, if it is an image file we may want to display the image; if it is a CSS file, use it as a stylesheet.

One particular case is displaying a video stream in a video element, when working with getUserMedia. The simplest way of doing this is to get a temporary, anonymised URL from the browser to use as we would any other URL – for example as the value of an image src attribute. In the HTML5 File API, the window object has a URL property. This has two methods:

  • createObjectURL: Creates a URL for a given file
  • revokeObjectURL: Destroys the reference between the URL and the file

We can't store a URL created in this way in, say, localStorage then reuse it in a different window, as it only persists for the life of the document while the window that created it is is open.

Here's how we might use this, in conjunction with an input of type file to get an image from the local file system and display it as a thumbnail. The input element is exactly as before:

<input type="file" id="chooseThumbnail" accept="image/*">
and here's the JavaScript
function showThumbNail(evt){

var url;
var file;

var thumbnail = document.querySelector("#thumbnail");
//this is the image element where we'll display the thumbnail

file = evt.target.files[0];
//because there's no 'multiple' attribute set on the input
//users can only select one
//we'd want to check this is the right sort of file

url = window.URL.createObjectURL(file);
//we create our URL (for WebKit browsers we need webkitURL.createObjectURL)
thumbnail.src = url;
//we give our thumbnail image element this URL as its source

}

We're done! No need to worry about asynchronous file reads or callback functions. When we only want to use a file, and not read its contents, this is by far the preferred solution.

Wrapping Up

The File API brings the browser closer to the native file system, allowing users to play, view and edit content from their local system directly in the browser with no need for these to be sent to a server.

It even allows us to access the camera and take photos using capture=camera as part of the type attribute's value. We will however still be waiting for some time (quite likely forever) to get full access to the native file system.

Don't miss John Allsopp's opening session – Predict the future with this one weird old trick – at Generate Sydney on 5 September.

Topics