New Adventures In HTML5: Part 2 (File API, Native Drag&Drop, Local Storage)

As developers we love building cutting-edge web apps for state-of-the-art browsers. Those are, fortunately, nowadays used be a vast majority of users (around 95% of all users in our 10 most recent projects). So for our newest project, we decided focusing on new technologies like HTML5 Canvas and the File API, among others. In this two-part article, we want to share our experiences using these techniques while building the Facebook app “tele.ring Poster-Creator”.

This is part 2 of the “New Adventures In HTML5” series. Be sure to read part 1 before continuing.

File API

One thing that makes the Poster-Creator feel like a native app is the JavaScript File API. The main feature here is that the user can “upload” a photo of arbitrary file size (only limited by the computer’s memory) without having to wait more than a split second before being able to work with it. That is because no image data leaves the user’s computer, which is why I put the word “upload” in quotes. Had we actually used regular file upload here, the user would have had to wait until the (potentially very large) image was transferred to the server, which would have been very unpleasant. The first time that data is uploaded from the client to the server is after the user has finished the poster.

Here’s a basic example of how to load an image from a simple HTML file input, using the File API:

We first check whether the browser supports the File API (the parts we care about are File and FileReader). If it does and the user has picked a file using the file input, we create a new FileReader and read the first file into a data-URL (only one of several possible output formats). We also attach an event listener to the FileReader which fires as soon as it has finished reading the file.

If you paid attention earlier, you possible already know why we chose to read the image into a data-URL: The last example from the Canvas part of this article shows you what the importImage function basically looks like: It takes the URL of an image, downloads it, and draws it onto the canvas. With data-URLs, the downloading part is skipped by the browser, enabling us to immediately draw it onto our canvas. All of this is possible without changing anything in the importImage function from earlier.

Drag and Drop Events

Lets face it: Even though importing images is lightning fast thanks to the File API, the user still has to click on a file input (which at least looks like a button in our app), clicking through the file system dialog that pops up, then picking the desired image and clicking again. There is just too much clicking going on! While this is the way some users also pick files on their computer, many prefer to simply grab a file from the desktop, dragging it into the program where they want to use it and dropping it there. This is, essentially, only one click, which is beautiful!

So in order to make our app really feel native, we have to enable this behavior in our app as well:

In this example we make the entire document a drop target, so the user can just drop the file anywhere on the page. In the first “if”, we check whether the browser supports data transfer. If it does, we check whether there are any files to be imported. If so, we take the first one and import it. The function importFromFile() is very similar to the previous code example. The last lines prevent the browser from simply displaying the image instead of importing it.

Keep in mind that you should also give the user visual feedback when and where to drop the file. Fortunately, there is also an event that fires as soon as a file is dragged into a drop zone, but before it is dropped. The event is called “dragenter”. Keep in mind that you should only display your visual feedback if the browser supports working with the file. IE9, for example, does support drag & drop events, but it does not support the File API.

There is a third event called “dragover” which fires repeatedly when dragging a file. You normally don’t need this, but you should attach an event handler to it anyway to prevent the browser from behaving in unexpected ways (for example when selecting text on the page and dragging that around). Just call e.preventDefault() and e.stopPropagation() inside the handler and you’re good.

Web Fonts (on a Canvas!)

Being able to render text on a canvas is great, but what if the client wants to use their CI font?

Well, if the font is available as a web font there is no reason to panic: Simply include the font as usual, for example via CSS, and set the canvas’ font to that before rendering the text. You have to pay attention when attempting this with KineticJS which seems to have problems with fonts with spaces in their name. Our workaround was to simply remove the spaces when loading the web font in CSS. As you can see, you can just rename the included font to anything you want:

There is one problem, though: Fonts are loaded asynchronously and there is absolutely no way (that we know of) to tell when they have completed loading. Some developers suggest loading the font in JavaScript as a fake image and then using the “error”-event handler, but this only tells you that JavaScript has finished loading the font. Not only does this mean that we have to load the same font twice, it is also possible that the real loading finishes after the fake one, even if it was started much earlier. So in fact this tells us nothing definite, so we don’t do it.

What we are doing instead: We simply ignore the fact that the font is not loaded and pretend it is. Canvas does not care and just uses a default font until the real one is ready. On the next redraw (which might be just some milliseconds away), everything looks perfect.

To shorten (or rather: get rid of) the “flash of the default font”, we pre-load the web font on the app’s landing page even though we are not using it there, so that the font is already in the browser cache before the user even gets to the canvas. That way, Canvas can immediately render its text in the right font.

Local Storage

While IE9 is a rather decent browser and supports almost every technology mentioned in this article, it does not support the File API. This means that if the users clicks the aforementioned upload button, we actually have to upload the file. Unfortunately, there is no easy way to upload a file from IE9 via AJAX, and we wanted to find a solution that required a minimum amount of IE9-specific code.

The solution we came up with only added a couple of lines of code to the app’s source and is using another rather new web technology: Local Storage.

Local Storage allows you to save data in the browser that is still there after the page refreshes. In that way, it is similar to cookies, but easier to handle and it allows us to store much more data. It is accessible via the variable localStorage and has three important methods: setItem(), getItem and removeItem(). Keep in mind that you can only store Strings in local storage, so if you want to store objects, you have to serialize them first, using JSON.stringify().

Here’s what we are doing: As soon as the picture upload (which is a form submission) is initialized, we stringify the state of the app (i.e. the scale and rotation of the user image and the entered text) and store it with Local Storage. When the upload is finished, the form submission triggers a reload of the page, after which we load the state from Local Storage and apply it to the app.

While this solution is not optimal, a user who has never seen the app in a current browser won’t really mind the increased loading time when uploading a photo. After all, the button does say “upload”! ;-)

Honorable Mention: Chrome Frame

So now that we have taken care of IE9, what about IE8? Well, in order to really support IE8, we would have had two options: Either dumb the app down, making it less awesome for everyone, or create a separate app only for older Internet Explorers, which would have made the app much more expensive. Since according to Google Analytics none of our Facebook apps that were active in the last couple of months had more than 5% IE8 users (the apps for this particular client only had about 2%!), this would not have made any sense.

So the thing to do was to redirect IE7 and IE8 users to a page explaining that their browser does not support the app, but there was a solution: installing Chrome Frame.

The Chrome Frame plugin essentially replaces IE’s browsing engine with Webkit. This means that when using this plugin, our app performs just as well as it does in Chrome. There are two other great things about Chrome Frame:

The user does not need admin privileges on the computer to install it.

The browsing engine is only swapped on pages that request it. So the rest of the web looks completely normal to the IE8 user while our app benefits from the capabilities of Webkit

Enabling Chrome Frame is incredibly simple. You only have to add the following meta tag to the head of your documents:

<meta http-equiv="X-UA-Compatible" content="chrome=IE8">

The content string “chrome=IE8” tells the browser to only enable Chrome Frame if it is an Internet Explorer 8 or older. The ironic thing is: Since we do support the native IE9, the app will perform much better in IE7 with Chrome Frame enabled than it does in IE9.

Just one more thing: We also tested the app in the preview version of Internet Explorer 10 and were delighted to see that the app worked flawlessly! There is a bright future ahead of us, fellow web developers!

Spread the ♥:

About Author

Patrick is a software developer at Die Socialisten. He is very passionate about JavaScript, HTML5, web standards and music. Some of his colleagues call him a hipster, but it's totally not his fault that he discovers everything before it gets big! Follow him on Twitter, Facebook or on Google+!