Adobe
Products
Acrobat
Creative Cloud
Creative Suite
Digital Marketing Suite
Digital Publishing Suite
Elements
Photoshop
Touch Apps
Student and Teacher Editions
More products
Solutions
Digital marketing
Digital media
Education
Financial services
Government
Web Experience Management
More solutions
Learning Help Downloads Company
Buy
Home use for personal and home office
Education for students, educators, and staff
Business for small and medium businesses
Licensing programs for businesses, schools, and government
Special offers
Search
 
Info Sign in
Welcome,
My cart
My orders My Adobe
My Adobe
My orders
My information
My preferences
My products and services
Sign out
Why sign in? Sign in to manage your account and access trial downloads, product extensions, community areas, and more.
Adobe
Products Sections Buy   Search  
Solutions Company
Help Learning
Sign in Sign out My orders My Adobe
Preorder Estimated Availability Date. Your credit card will not be charged until the product is shipped. Estimated availability date is subject to change. Preorder Estimated Availability Date. Your credit card will not be charged until the product is ready to download. Estimated availability date is subject to change.
Qty:
Purchase requires verification of academic eligibility
Subtotal
Review and Checkout
Adobe Developer Connection / HTML5, CSS3, and JavaScript /

Real-world example of the HTML5 FileSystem API

by Raymond Camden

Raymond Camden
  • raymondcamden.com

Content

  • Initializing and Preparing the File System
  • Where to go from here

Created

30 April 2012

Page tools

Share on Facebook
Share on Twitter
Share on LinkedIn
Bookmark
Print
HTML5 JavaScript

Requirements

Prerequisite knowledge

Some basic knowledge of HTML5 and JavaScript. Basic knowledge of the FileSystem API is helpful.

 

Additional required other products

  • Any browser supporting the FileSystem API – currently only the latest Chrome. You can also check When can I use Filesystem & FileWriter API?.

User level

Intermediate

Sample files

  • cfjedimaster-HTML-Code-Demos-352e549.zip

A quick note about the code in this article before we begin: the code in this article is not meant to be typed in piece by piece. Instead, download the zip and extract it to a local web server. You cannot run it by double-clicking on the file. The code must run via a working web server, although that server can certainly be your local development web server.

One of the more powerful features coming soon to a browser near you is the ability to read and write to the local file system. You can find the File API: Directories and System working draft on the W3C website. Dubbed the FileSystem API, it provides the browser a safe little sandbox where data (both textual and binary) can be read and written. Many excellent articles out there detail the API. (I'd start with the excellent one from HTML5Rocks.com: Exploring the FileSystem APIs.) This article assumes you have at least a familiarity with the basics. With that assumption, we're going to look at a real world scenario where browser file system access can be useful – the local caching of media resources.

Imagine for a moment you are creating a hip, new web property. You want to make use of several high quality images or sound files. Imagine if you could package up all these resources into one zip file, send it to the browser, and have it store a copy locally? This is not necessarily for offline access, but as a way to minimize remote network calls and simply offload some of the "weight" to the client. To be even more efficient, you also want to track the date the zip file was last updated. You can then use a lightweight network request to see if it has been updated before you go through the work of processing it again. Let's get started!

Initializing and Preparing the File System

As I described above, this article isn't meant to introduce you to the FileSystem API. My hope is that you read the related resources I mentioned. But if you're anything like me, you probably skipped that step. We all do. One of the first things the demo code does is determine if it can even use the FileSystem API feature. Right now the API is fraught with vendor prefixes. In the future, the API needs to be generalized, but here, we start off caring only about Chrome. Notice a variety of vendor prefixes in the code, like the following example:

function init() { if(!window.webkitStorageInfo) return;

(In case you're curious, the init function is being run via a body/onload call. The full template is below.) The initialization routine begins by checking for the existence of webkitStorageInfo. Because this demo is only concerned with demonstrating File System API stuff, we can immediately quit if it isn't supported.

The File System API differentiates between a temporary and persistent file system. Their very names indicate when you would use one over the other. For this application, choose a persistent file system so you can store your resources until they have to be updated. To work with the persistent file system, you request access from the user. This is done via a JavaScript function, but the actual prompt is handled by the browser, much like geolocation. The following code block demonstrates how to request the storage and what will be done after it has been approved (or denied):

window.webkitStorageInfo.requestQuota(window.PERSISTENT, 20*1024*1024, function(grantedBytes) { console.log("I was granted "+grantedBytes+" bytes."); window.webkitRequestFileSystem(window.PERSISTENT, grantedBytes, onInitFs, errorHandler); }, errorHandler);

Note that you request a size, but it's possible the size given may be smaller. Don't even worry about what you're given for now. In the future, you may want to record this ( localStorage ) and ensure you stay within your quota. But the important thing to note here is that once you've been approved a bucket of space, you can request the actual file system.

Here's an example of what the user sees using the latest Chrome. This UI may change in the future.

Figure 1. Chrome UI notification of file system storage.
Figure 1. Chrome UI notification of file system storage.

The call there, webkitRequestFileSystem , essentially returns a pointer for all future read/write file and directory options. It's success handler, in this case onInitFs , is run once you're good to go. Finally, our errorHandler is run if anything goes wrong. Take a quick look at that before going on:

function errorHandler(e) { var msg = ''; console.dir(e); switch (e.code) { case FileError.QUOTA_EXCEEDED_ERR: msg = 'QUOTA_EXCEEDED_ERR'; break; case FileError.NOT_FOUND_ERR: msg = 'NOT_FOUND_ERR'; break; case FileError.SECURITY_ERR: msg = 'SECURITY_ERR'; break; case FileError.INVALID_MODIFICATION_ERR: msg = 'INVALID_MODIFICATION_ERR'; break; case FileError.INVALID_STATE_ERR: msg = 'INVALID_STATE_ERR'; break; default: msg = 'Unknown Error'; break; }; console.log('Error: ' + msg); }

The above code was taken (and slightly modified) from the HTML5 Rocks article. It's just for testing and doesn't actually present any nice response to the user. It only uses the console to report errors. Be sure you do your testing with the console open. (Don't all good JavaScript developers do that?).

Working with the File System

So at this point, you've established that your browser supports a file system. You've requested storage from the user. And you've asked for a pointer to the file system. After all of that, onInitFs is finally run.

It's probably a good idea to refresh to clarify the goal at this point. The goal is to download a zip file, extract the contents, and store it on the local file system. To enable that, begin by defining a folder where your files are stored. Call this variable resourceDIRLOC :

var resourceDIRLOC = "resources";

There isn't anything special about this name, but you want a subdirectory to add more stuff in the future, and not have to worry about organization. Even though this is a sandbox separated from the rest of the file system, it's important to think of this as any other file system. You don't want to make mess. Both for your user's sake and for your own sanity.

First, open up this directory. The API allows you to open a directory that doesn't exist. We do this by passing a create flag. You can only do this for one level of directories at a time. So for example, you can't try to open /resources/images/highres and have the API simply create all those nested folders. In a case like that, you need to create each subdirectory one at a time. Luckily, this example has a simplier target:

function onInitFs(fs) { fileSystem = fs; fileSystem.root.getDirectory(fs.root.fullPath + '/' + resourceDIRLOC, {create:true}, function(dir) { resourceDIR = dir;

Copy the file system handle, fs , to a globally-scoped variable. You need fs later so it's best to copy it right away. Next, call to get the directory. Notice the path is based on one of the properties of the file system handle – root.fullPath . The root object is a directory pointer to the path of our sandbox. The fullPath is simply that – the actual directory path. Combining that with a separator (and note – you can use / whether or not you are on Windows) and the resource directory name, you then have a complete path to the folder to use. The create flag handles the first-time creation. All calls to the file system API are asynchronous, so begin a callback function in the last argument. Finally, the very first thing we do in the callback is cache a pointer to the new directory. resourceDIR is a global variable to use again later.

Now for the interesting part: the zip file you download is pretty large. You only want to download it the first time, and after that, only if it's been modified. To remember the modification date, use localStorage to cache it. Consider the next block:

if(localStorage["resourceLastModified"]) { var xhr = new XMLHttpRequest(); xhr.open("HEAD", resourceURL ); xhr.onload = function(e) { if(this.status == 200) { if(this.getResponseHeader("Last-Modified") != localStorage["resourceLastModified"]) { fetchResource(); } else { console.log("Not fetching the zip, my copy is kosher."); } } } xhr.send(); } else { fetchResource(); }

The first portion of the code block above executes if you have a value for when the zip was last modified. (Soon you will see where to set that.) If resourceLastModified exists, you create a HEAD -only Ajax request. This is a light-weight network call that just returns the headers of the remote resource. We check the Last-Modified header. If it is different in anyway, we need to re-get our zip file. That's done in the fetchResource() call. Finally, you see the else block simply runs fetchResource() .

Getting and Processing the Zip File

Let's take a look at the fetchResource() method. It's responsible for getting the remote zip file, unzipping it, and saving it to the file system. JavaScript doesn't have native support for working with zip files. I used the simple, yet powerful, zip.js library written by Gildas Lormeau. You can find the zip.js library on GitHub. Note that you only need the files zip.js and deflate.js .

Let's begin looking at fetchResource :

function fetchResource() { var xhr = new XMLHttpRequest(); xhr.responseType="arraybuffer"; xhr.open("GET", resourceURL,true); xhr.onload = function(e) { if(this.status == 200) { } } xhr.send(); }

The code above shows the portions of the function that handle the Ajax request. For now, the onload is empty, because it's a bit complex. Note a few things. First, the response type is arraybuffer . You need this to process the binary data from the zip. Secondly, the resourceURL is simply a static url defined earlier in our code:

var resourceURL = "resources.zip";

Now dig into the code run when the request is done:

localStorage["resourceLastModified"] = this.getResponseHeader("Last-Modified");

The very first thing you do is cache the date the zip was modified. LocalStorage makes this incredibly easy to use. Make note of that key there, resourceLastModified . You can test the code multiple times. Either build new zips and update their last modified value via the command line, or simply use your browser's console to delete the value.

var bb = new WebKitBlobBuilder(); bb.append(this.response); var blob = bb.getBlob("application/zip");

Next, prepare the binary data before handing it off to the zip library. This is a multi-step process that involves a "Builder" sourced by the raw response and then the actual Blob object created by specifying our particular MIME type for our data. The end result, though, is zip binary data. Now, parse the zip:

zip.createReader(new zip.BlobReader(blob), function(reader) { reader.getEntries(function(entries) { entries.forEach(function(entry) { resourceDIR.getFile(entry.filename, {create:true}, function(file) { entry.getData(new zip.FileWriter(file), function(e) { }, function(current, total) { // onprogress callback }); }); }); }); }, function(err) { console.log("zip reader error!"); console.dir(err); })

The code above is probably a bit confusing as you have callbacks calling callbacks. In a nut shell, you begin by creating an instance of a zip reader. This is based on the zip.js API. One of the many ways to initialize the zip reader instance is by passing in our blob object. You then provide a callback to handle the reader. Within that, you call getEntries on the reader. This allows you to enumerate over each item in the zip file.

This is the point where you begin writing data to the file system. Remember resourceDIR ? It's just a pointer to our directory. Use it to create files within it by calling getFile . Pass in a name, based on the zip file entry name. So, if the first entry in our zip is foo.jpg , entry.filename is foo.jpg . getFile opens the file on the file system. Within the success handler, you then use entry , which is the file in the zip, and suck the data out with getData . That was probably even more confusing. Essentially, you open a file on the file system and siphon out the bits from the zip file entry into the file you opened. The first argument to getData is a file writer. That handles the actual bits. Two empty callbacks in there could optionally monitor the progress. But since this is a relatively simply process (again, sucking the bits from one thing to another), you can leave them alone for now.

And that – as they say – is it. In order to test, I first made use of an excellent Chrome plugin called Peephole. Peephole is an extension that lets you browse the file system associated with a website.

Figure 2. Peephole browses the file system associated with a website.
Figure 2. Peephole browses the file system associated with a website.

The files listed in the figure, above, are all images from the zip file. I also built a simple function that renders a few of these images:

document.querySelector("#testButton").addEventListener("click", function() { //Attempt to draw our images that exist in the file system //If they exist, we draw from there, if not, we do not display them. var images = ["bobapony.jpg","buy bacon.jpg","cool boba.jpg","chuck-norris.jpg"]; for(var i=0, len=images.length; i<len; i++) { var thisImage = images[i]; resourceDIR.getFile(thisImage, {create:false}, function(file) { document.querySelector("#images").innerHTML += "<img src='"+file.toURL() + "'><br/>"; }); } }, false);

After the user clicks on a button, I loop over an array of file names and see if they exist in the file system. If so, I simply add an image to the DOM. Note the use of file.toURL() . I use this call to get a reference to the image that I can then reference from HTML.

Where to go from here

I hope this article gives you an idea of what could be done with the file system. While support is still somewhat limited, the benefits of being able to store resources locally make it more than worthwhile even if the API is a work-in-progress. Keep your eye on the File API W3C working draft for progress.

More Like This

  • Introducing the HTML5 storage APIs
  • Introducing theexpressiveweb.com beta
  • Adobe, standards, and HTML5
  • Developing HTML5 games with Impact JavaScript game engine and Dreamweaver CS5.5
  • Using the Geolocation API
  • CSS3 basics
  • Real-time data exchange in HTML5 with WebSockets
  • Backbone.js Wine Cellar tutorial – Part 2: CRUD
  • JavaScript object creation: Learning to live without "new"
  • Backbone.js Wine Cellar tutorial – Part 1: Getting started

Products

  • Acrobat
  • Creative Cloud
  • Creative Suite
  • Digital Marketing Suite
  • Digital Publishing Suite
  • Elements
  • Mobile Apps
  • Photoshop
  • Touch Apps
  • Student and Teacher Editions

Solutions

  • Digital marketing
  • Digital media
  • Web Experience Management

Industries

  • Education
  • Financial services
  • Government

Help

  • Product help centers
  • Orders and returns
  • Downloading and installing
  • My Adobe

Learning

  • Adobe Developer Connection
  • Adobe TV
  • Training and certification
  • Forums
  • Design Center

Ways to buy

  • For personal and home office
  • For students, educators, and staff
  • For small and medium businesses
  • For businesses, schools, and government
  • Special offers

Downloads

  • Adobe Reader
  • Adobe Flash Player
  • Adobe AIR
  • Adobe Shockwave Player

Company

  • News room
  • Partner programs
  • Corporate social responsibility
  • Career opportunities
  • Investor Relations
  • Events
  • Legal
  • Security
  • Contact Adobe
Choose your region United States (Change)
Choose your region Close

North America

Europe, Middle East and Africa

Asia Pacific

  • Canada - English
  • Canada - Français
  • Latinoamérica
  • México
  • United States

South America

  • Brasil
  • Africa - English
  • Österreich - Deutsch
  • Belgium - English
  • Belgique - Français
  • België - Nederlands
  • България
  • Hrvatska
  • Česká republika
  • Danmark
  • Eastern Europe - English
  • Eesti
  • Suomi
  • France
  • Deutschland
  • Magyarország
  • Ireland
  • Israel - English
  • ישראל - עברית
  • Italia
  • Latvija
  • Lietuva
  • Luxembourg - Deutsch
  • Luxembourg - English
  • Luxembourg - Français
  • الشرق الأوسط وشمال أفريقيا - اللغة العربية
  • Middle East and North Africa - English
  • Moyen-Orient et Afrique du Nord - Français
  • Nederland
  • Norge
  • Polska
  • Portugal
  • România
  • Россия
  • Srbija
  • Slovensko
  • Slovenija
  • España
  • Sverige
  • Schweiz - Deutsch
  • Suisse - Français
  • Svizzera - Italiano
  • Türkiye
  • Україна
  • United Kingdom
  • Australia
  • 中国
  • 中國香港特別行政區
  • Hong Kong S.A.R. of China
  • India - English
  • 日本
  • 한국
  • New Zealand
  • 台灣

Southeast Asia

  • Includes Indonesia, Malaysia, Philippines, Singapore, Thailand, and Vietnam - English

Copyright © 2012 Adobe Systems Incorporated. All rights reserved.

Terms of Use | Privacy Policy and Cookies (Updated)

Ad Choices

Reviewed by TRUSTe: site privacy statement