The File Object

Chapter 17 13 mins

Learning outcomes:

  1. Where to obtain File objects
  2. The File interface
  3. Practical examples

Introduction

In this chapter we shall begin with getting some intuition behind the File API in JavaScript, particularly looking into how to use it to find useful information about a given file. Let's begin...

Accessing a file in JavaScript

There are primarily two ways to access a file, from the local filesystem, in JavaScript and they are as follows:

  1. Selecting a file using an input element with type="file"
  2. Dragging and dropping a file in a drop zone.

In both cases, the selected files can be accessed via the files object, on the input element and the dataTransfer property of the DragEvent argument.

Following we demonstrate both these cases to ultimately retrieve a File object, for each of the files we select.

Using the input element

When working with a file input element, it's quite common to give it an onchange handler which can respond to the action of browsing and then selecting a file in it.

Consider the code below:

<input type="file" id="fileInput">
var fileInput = document.getElementById("fileInput");

fileInput.onchange = function(e) {
    // we'll understand this property just shortly!
    var files = this.files;
}

When you initially interact with the resulting file input interface here i.e browse your local filesystem and finally select a file, you indirectly perform the change event and thus cause the respective onchange handler to fire.

Just think of it: when you select a new file, you get the input element's value to change - hence the name change for the corresponding event!

In this case the handler saves the list of all the files selected in a local variable files, from the files property on the input element.

The files property of a file input element is a FileList object which holds a File object for each of the selected files.

The most important thing to realise here is that - the files property is accessible through the input element, which is the this value inside the onchange handler shown above.

In the next section we shall see what is hidden under every File object.

Using the drag-and-drop API

Apart from using the input element to allow a user to browse and ultimately select a file, another way to accomplish this is using the drag-and-drop API.

The idea is simple - the user picks up a file outside the browser, drags it into the browser window and finally drops it into a special area known as a drop zone.

The drop zone is made to respond to drop actions and consequently extract information out of the dropped items. This is done by handling the drop and dragover events occuring on the respective element made the drop zone.

Let's implement a simple drag-and-drop utility:

First we create an element to be converted into a drop zone.

<div id="drop-zone"></div>

Then we give this element the ondragover and ondrop event handlers (to turn it into a drop zone).

var dropZone = document.getElementById("drop-zone");

dropZone.ondragover = function(e) {
    // prevent the default behavior
    e.preventDefault();
}

dropZone.ondrop = function(e) {
    // prevent the default behavior
    e.preventDefault();

    var files = e.dataTransfer.files;
}
The e.preventDefault() calls here are extremely important! Omitting them would resume the default dropping behavior, where the dropped file will be displayed in the browser window.

Inside the ondrop handler (in line 8), we use the event argument e to access the dropped file.

This event argument contains a dataTransfer property that holds information about the dropped item. If we go further down the dataTransfer object, we notice a files property.

This files property operates exactly the same way as the one on the input element, we saw above. It's also a FileList collection with individual File objects containing information about each of the dropped files.

Now that we know where to start accessing files in JavaScript, it's time that we dive right into inspecting the individual file objects.

Inspecting a file

As highlighted earlier, the files property holds a list of all the files selected using one of the file selection UIs discussed above. The individual elements in this list are all File objects.

So what is a File object and how does one look like?

In simple words:

A File object represents a file, fetched from the local filesystem.

A File object is used to hold the actual data of a file. Along this data, it also contains some useful information about the file.

But what information exactly? Essentially the following:

  1. Name of the file
  2. Its size, in bytes
  3. Its MIME type, for example text/plain for text files, text/html for HTML files and so on..
  4. Its Last Modified date

Consider the following example to get a real overview of the information held on by a File object.

Suppose we select a file foods.txt, using the HTML input interface, with the information shown below:

Name: foods.txt
Size: 562 Bytes
Last Modified: Fri, 23 Aug 2019 16:45:49

The files property of the input element will return us a FileList object with a File object for each of the selected files.

Since we're assuming we've have selected only one file, it will be accessible via referring to the first element of the FileList object, at index 0.

The resulting File object will have the following properties:

  1. name with the value "foods.txt"
  2. size with the value 562
  3. type with the value "text/plain"
  4. lastModifiedDate holding a Date() object to denote the date shown in the snippet above.
  5. lastModified corresponding to the number of milliseconds since the the Unix Epoch to the lastModifiedDate.
Read more about Unix Epoch at JavaScript Dates.

The first three properties are fairly handy to us.

For example, we can show what file a user selected by displaying its name using name; or ensure that a file doesn't exceed a certain size limit by checking size or that it's only one of some given file types by checking type.

The last two aren't completely useless, but, at least, not on the same level as the former set of properties.

So now that even the File API is examined, why not move over to consider some real practical examples, and in the way also explore some of the methods on FileList.

Practical examples

First we'll see how to monitor file changes in an input element and log information for the new file selected.

The process is very simple - handle the change event on the input element, retrieve the first element in its files property, and finally extract information out of it.

<input type="file" id="fileInput">
var fileInput = document.getElementById("fileInput");

fileInput.onchange = function(e) {
    // retrieve the first File element
    var file = this.files[0];

    console.log("name:", file.name);
    console.log("size:", file.size);
    console.log("type:", file.type);
}
The reason we select the first element here, in the files property (in line 5), is that we've omitted the multiple attribute on the input element. This means that only one file is allowed to be selected at a time, which in turn means that files will be a list with at most one element.

Now imagine that we select the foods.txt file, we saw in the section above, with this code in place. The console will then look something similar to the following:

name: foods.txt
size: 562
type: text/plain

The properties are equal to exactly what we had expected them to be equal to in the section above.

Next, let's consider a slightly more complicated example.

We'll see how to process multiple file objects selected using an input element, to display all their information in a table.

Consider the following code:

<input type="file" id="fileInput" multiple>

<table>
    <thead>
        <tr><th>Name</th><th>Type</th><th>Size</th></tr>
    </thead>
    <tbody id="desc"></tbody>
</table>

Very simply, we have an input element with the multiple attribute on, to select multiple files; and a table to show all their data.

The scripting part goes as follows:

var fileInput = document.getElementById("fileInput");
var tableBody = document.getElementById("desc");

fileInput.onchange = function(e) {
    var files = this.files;
    var html = [];

    // iterate over all files
    for (var file of files) {
        // generate row markup for each file
        html.push(`<td>${file.name}</td><td>${file.type}</td><td>${file.size}</td>`);
    }

    // finally create all table rows
    // and add them the tbody element
    tableBody.innerHTML = '<tr>' + html.join('</tr><tr>') + '</tr>';
}

Live Example

The input element is selected in fileInput and assigned an onchange handler to take care of all the display logic.

Whenever one selects a bunch of files, the change event fires and consequently the onchange handler shown above gets fired.

Inside the handler, we iterate over all the elements in the files object of the input element using the for...of loop, and deal with each File element separately.

For each File element, we retrieve its name, size and type, and put these inside a formatted markup string, and finally push this string onto an html array.

The reason of doing so is to avoid string concatenation - it's relatively inefficient as compared to pushing all the strings in an array and then concatenating them just once!

Don't avoid all your string concatenation cases - only avoid those where you think you're about to do a lot of concatenation!

Even in our example, we weren't concatenating a lot of files. Nonetheless we used the array method only to demonstrate how to make even simple-looking programs more efficient!

Finally, we join all the required markup and place it inside the <tbody> element.

This completes our, so-called, complicated example. You might probably agree that it wasn't that complicated, do you?

Construct a file input interface that only accepts a single file less than or equal to 200KB.

If the file is less than 200KB, log "OK" otherwise log "Limit Exceeded".

The property size holds the size of the selected file, in units of bytes. Therefore, in one way, we check size against the value 200000 (200 * 1000).

<input type="file" id="fileInput">
var fileInput = document.getElementById("fileInput");

fileInput.onchange = function(e) {
    var file = this.files[0];
    if (file) {
        if (file.size <= 200000)
            console.log("OK");
        else
            console.log("Limit Exceeded!");
    }
}

In line 5, we check file in order to ensure that a file is actually selected in the input element while we are in the onchange handler.