File Constructor



A set of APIs for representing file objects in Web applications has been expanded: one of the newest features in Firefox is the implementation of the File constructor method. The File interface inherits its attributes from Blob, so creating an instance of a File is similar to the instantiation of a Blob. A comma-delimited list of constructor parameters contains an array, a file name and an optional file properties represented as a BlobPropertyBag dictionary. The first parameter must be an array of strings, ArrayBuffer objects, buffer views, or Blobs. A constructor declaration with strings can look like this:

var file=new File(["Actions ", "speak ", "louder ", "than ", "words"], "proverb.txt");
var fileReader=new FileReader();
fileReader.onload=function(event) {
 console.log(event.target.result);
}
fileReader.readAsText(file);

A BlobPropertyBag dictionary is passed to the constructor to indicate a MIME type of the file and a rule for handling string endings:

var fileProperties={type: "text/plain", endings: "native"};
var file=new File(["Best ", "defense ", "is ", "attack"], "proverb.txt", fileProperties);
console.log(file.type); // text/plain

An array of ArrayBuffer objects can be used to create a File with opaque data:

var buffer=new ArrayBuffer(512); // creates a new ArrayBuffer of 512 bytes
var file=new File([buffer], "bytes.dat");

The length of the buffer in the example above is 512 bytes. The new binary file will have the same size. However, It doesn't expose any data visible to the user: the contents of the ArrayBuffer are initialized to 0.

A more complex scenario may presume the use of an XMLHttpRequest with the "arraybuffer" response type. Let's assume a large audio file is split up and has four parts. Each part is stored separately. A JavaScript code for clients with the intermittent or slow Internet connection can download the large file in chunks. A list of URLs where parts are located is kept in a JSON file on the server:

var array=new Array(); // array argument for the File constructor

var xhr=new XMLHttpRequest();
xhr.onreadystatechange=parseJSON;
xhr.open("GET", "urls.json");
xhr.responseType="json";
xhr.send();

The JSON file can have the following structure:

{
 "audio": [
  "http://cdn1.example.com/part1.bin",
  "http://cdn2.example.com/part2.bin",
  "http://cdn3.example.com/part3.bin",
  "http://cdn4.example.com/part4.bin"
 ]
}

Each URL from the list is used to fetch a part of the large file:

function parseJSON(event) {
 if(this.readyState==4) { // or event.target.readyState
  if(this.status==200) { // or event.target.status
   var urls=this.response.audio; // array of four elements
   for(var i=0; i<urls.length; i++) {
    fetchResource(urls[i]);
   }
  }
 }
}

function fetchResource(url) {
 var rq=new XMLHttpRequest();
 rq.onreadystatechange=fillArray;
 rq.open("GET", url);
 rq.responseType="arraybuffer";
 rq.send();
}

After a part of the large file has been retrieved from the server, ii is added to the array as an ArrayBuffer object:

function fillArray(event) {
 if(this.readyState==4) {
  if(this.status==200) {
   console.log("part of the file is downloaded successfully . . .");
   array.push(this.response); // ArrayBuffer object is added to the array
   if(array.length==4) { // all parts are downloaded
    combineParts(array);
   }
  }
 }
}

When all parts of the large file are fetched from the Web, they are assembled into a single audio file:

function combineParts(parts) {
 var file=new File(parts, "audio-file.mp3", {type: "audio/mpeg"});
 var url=URL.createObjectURL(file); // blob: URL scheme
 var link=document.createElement("a");
 link.href=url; link.download=file.name;
 link.appendChild(document.createTextNode("download audio file"));
 document.body.appendChild(link);
}

ArrayBuffer objects are useful for transferring chunks of raw binary data, but if we want to represent data in a more transparent format, we'll have to instantiate an ArrayBufferView. The code snippet below builds a typed array with code points for 4 characters ('D', 'E', 'M','O'), then creates a text file containing the word "DEMO" assembled from the typed array elements:

var buffer=new ArrayBuffer(4); // ArrayBuffer of 4 bytes
var view=new Int8Array(buffer); // signed integer array will represent the buffer
view[0]=68; view[1]=69; view[2]=77; view[3]=79; // code points for 4 ASCII characters
var file=new File([view], "demo.txt", {type: "text/plain"});

There are 9 types of views that can be used for representing byte buffers:

Typed Array View Bytes Per Element
Int8Array
Uint8Array
Uint8ClampedArray
Int16Array
Uint16Array
Int32Array
Uint32Array
Float32Array
Float64Array

Supplying binary data to the File constructor is not restricted to the use of byte buffers only: Blob objects can be employed to fill the array passed to the constructor method. For example, local text files are combined to build a large file and display it in a visual text container:

<input type="file" multiple onchange="buildLargeFile(this.files);">
. . .
function buildLargeFile(files) {
 var array=new Array();
 for(var i=0; i<files.length; i++) {
  array.push(files[i]);
 }
 var file=new File(array, "compilation.txt", {type: "text/plain"});
 var fileReader=new FileReader();
 fileReader.onload=function(event) {
  document.getElementById("doc-container").innerHTML=event.target.result;
 }
 fileReader.readAsText(file);
}

Similar to the Blob constructor, the File constructor method can accept objects of various types simultaneously:

var title="proverbs:\n";
var proverbs=new Blob(["Love conquers all", "\n", "Love laughs at locksmiths"]);
var file=new File([title, proverbs], "proverbs.txt", {type: "text/plain", endings: "native"});