HTML5

HTML5_Logo_512.png

GoDaddy and AppCache Manifest Files

1

In trying to add HTML5 Application Cache support to one of my web apps today, I hit a few little snags with my hoster (GoDaddy with a Shared Deluxe Windows account) and wanted to share as I’m sure other people will run into the same thing.

Each time I uploaded my web.config file, I kept getting 500 server errors that weren’t very helpful.  As it turns out, the extension .manifest is already taken by the mime-type application/x-ms-manifest, and when I tried to override that, IIS got kind of angry, and served the 500 Server error.  I wasn’t quite sure what was happening, but was able to figure it out by reverting to my original web.config file, and requesting the appcache.manifest file that I had already uploaded.  Sure enough, using the network tab in the Chrome DevTools, I saw it was being served back with the wrong mime type.

To resolve the issue, I added a new static file handler for .appcache files in the system.web -> httpHandlers section.  If you don’t do this, IIS doesn’t know about the file type and it won’t serve unknown file types at all.  Then in system.webServer -> staticContent, I added a mimeMap extension for .appcache files with the mimetype of text/cache-manifest.  After uploading the web.config file again, I retried my request, and sure enough, everything worked perfectly!

My final web.config now looks like…

<configuration>
  <system.web>
    ...
    <httpHandlers>
      ...
      <add verb="GET,HEAD" path="*.appcache"
        type="System.Web.StaticFileHandler" />
    </httpHandlers>
  </system.web>
  ...
  <system.webServer>
  ...
    <staticContent>
    <mimeMap fileExtension=".appcache"
      mimeType="text/cache-manifest" />
    ...
    </staticContent>
    ...
  </system.webServer>
  ...
</config>

Check out this tutorial on Application Cache at HTML5Rocks.com. Also, you can find a full list of the default mime types provided by GoDaddy’s IIS servers here.

[Update 6/30/11 @ 1:53pm] @Paul_Irish pointed out that the recommended extension is .appcache specifically to avoid the unregistered Microsoft extension, and referred to a bug on HTML5.org. So there you have it! :)

HTML5_Performance_512

TweeterFall – Web Workers

1

How often have you seen this dialog, or something similar – telling you that some scripts on a page are taking too long while your browser has become completely unresponsive. Because JavaScript runs in the same thread as the rest of the browser UI, we’re often faced with the challenge of being unable to run any complex or long running JavaScript without the fear of blocking the browser. That’s where web workers come in.

Web Workers are part of the HTML5 specification and allow you to run scripts in the background, independently of the main browser thread. This means that you can do complex calculations or run scripts that may take a while to run without yielding to keep the page responsive.  The W3C spec has some great examples of use cases and other helpful descriptions that are worth reading for deeper details.  It also notes that workers are relatively heavy weight and are not intended to be used in large numbers.

In order to to maintain thread safety, workers run in an effective sandbox, which means they don’t have access to the DOM or many other components.  To get information back and forth between the worker and the main application, we need to use the postMessage method and listen for message events.

In TweeterFall, we used a worker thread poll Twitter for new tweets and then we ‘pushed’ those new tweets to the main thread.  Let’s have a look how we used workers.

Starting & Stopping the Worker

We only ever have one working running in our application, and to maintain an easy handle to it, we defined it’s variable in the global scope.  We then had two methods that would handle how the worker would behave – startWorker() and stopWorker(). The startWorker() method takes a parameter that provides information about the username or list that we want to pull tweets from, how often we should poll Twitter and the ID of the last tweet we saw.

var myWorker;

function startWorker(settings) {
  settings.cmd = "start";
  settings.lastTweetID = lastTweetID();
  console.log("WebWorker: Starting", settings);
  myWorker = new Worker("scripts/worker.js");
  myWorker.addEventListener("message", newTweetHandler, false);
  myWorker.postMessage(settings);
}

function stopWorker() {
  if (myWorker != undefined) {
    console.log("WebWorker: Terminating");
    myWorker.terminate();
    myWorker = null;
  }
}

In our startWorker() thread, we create the new worker with myWorker = new Worker("scripts/worker.js");, and then add an event listener and listen for any messages that the worker wants to pass back to us – this is the only way we’re going to be able to communicate to the worker because it’s working off in it’s own little isolated world. At this point, the worker exists, and if it wants to tell us anything, we’re ready to listen, but now we need to tell it to do something, which is the myWorker.postMessage(settings);.

Stopping our worker is pretty similar, we check to make sure that we have a valid object, and as long as we do, .terminate() will immediately close the worker without giving it an opportunity to shut down properly or clean up after itself. This works in our case because we don’t have anything in memory and we’re not doing any calculations of any kind. If you may need to save state or want to clean up after yourself, the better way to shut down would be to send a stop message via .postMessage() and within your worker thread, shut down gracefully by calling self.close(); after completing any clean up.

The Worker Thread

Inside our worker thread, we need to set up an event listener to listen for messages from our parent thread and any other code that we want to execute.

self.addEventListener('message', function(e) {
	var data = e.data;
	switch (data.cmd) {
		case 'start':
		  self.postMessage("Worker: Starting");
			setConfig(data);
			readTweets();
			break;
		case 'stop':
		  self.postMessage("Worker: Stopping");
			self.close();
			break;
		default:
		  self.postMessage("Worker: Error - Unknown Command");
	};
}, false);

Our event listener listens for messages from the parent and will start or stop the worker as necessary. When we get the start command, we parse the settings data (setConfig), and then start reading tweets (readTweets()). For sake of readability, I’ll skip the code we used for setConfig(), but it’s easy enough to find if you want it.

function getURL() {
  var sinceID = "";
  if (parseInt(lastKnownTweetID, 10) > 0) {
    sinceID = "&since_id=" + lastKnownTweetID;
  }
  if (typeof userList === 'undefined') {
    return 'http://twitter.com/statuses/user_timeline/' + twitterUser
      + '.json?count=' + tweetCount+ '&callback=processTweets' + sinceID;
  } else {
    //For a list
    return 'http://api.twitter.com/1/' + twitterUser + '/lists/' +
      userList + '/statuses.json?callback=processTweets' + sinceID;
  }
}

function readTweets() {
  try {
    var url = getURL();
    self.postMessage("Worker: Attempting To Read Tweets From - " + url);
    importScripts(url);
  }
  catch (e) {
    self.postMessage("Worker: Error - " + e.message);
    setTimeout(readTweets, 120000);
  }
}

readTweets() is the function that does the dirty work, making a JSONP call to Twitter that requests the latest tweets. We encapsulated the URL generation into it’s own method so that we could use it even if we weren’t using workers. Because we don’t have access to the DOM, we can’t just insert a <script> block, so after getting the URL, we call importScripts(url);, which makes an synchronous call and injects the result into the worker. If we run into any problems, like over capacity, not being able to reach Twitter or anything else, it’s caught in the catch block, and we wait two minutes before trying again. Here’s the JSONP call we make http://api.twitter.com/1/petele/lists/chromedevrel-11/statuses.json?callBack=processTweets.

The request we sent to Twitter includes ?callback=processTweets in the query string which will cause processTweets() to be executed once we’ve pulled the new tweets from Twitter.

function processTweets(data) {
  var len = data.length;
  for (var i = 0; i < len; i++) {
    if (data[i].id_str >= lastKnownTweetID) {
      lastKnownTweetID = data[i].id_str;
    }
  }
  returnTweets(data);
}

function returnTweets(data) {
  if (data.length > 0) {
    self.postMessage("Worker: New Tweets - " + data.length);
    self.postMessage(data);
  } else {
    self.postMessage("Worker: New Tweets - 0");
  }
  setTimeout(readTweets, updateDelay);
}

processTweets() enumerates all of the tweets we got back to find the most recent tweet ID. That way, when we ask Twitter again later for tweets, we’re only asking for the ones that we haven’t seen. Once we’ve done that, we call returnTweets() to push the new tweets back to the application. We could have merged processTweets() and returnTweets(), and I don’t remember why I decided to do it as two separate methods. Once we’re returned the tweets via returnTweets() we use setTimeout() to call readTweets() again.

A couple of quick tips

A couple things to remember about web workers, no DOM access means that you can’t manipulate the DOM, so that means that if you want to create new UI components in threads, you can, but you need to do it in the thread, and then pass that back to the main application via postMessage(). Unfortunately, it also means that jQuery doesn’t work in threads, so generating those UI components becomes a little more complex. I’ve heard rumors of a worker safe jQuery library, but haven’t seen it yet. Hopefully! :)

The other good thing to know is that you can debug works in the Chrome Developer Tools by clicking on the Scripts tab, and scrolling down in the column on the right to Workers and clicking on the debug checkbox.

Summary

I found using workers to be really easy, and a good way of handling this. There are plenty of other good ways to use workers – and it gives us some pretty good power to be able to push complex work down to the client. Especially as browsers JavaScript engines are getting faster and faster.

Additional Resources

HTML5_Performance_512

TweeterFall – Notifications

0

In TweeterFall, when a new tweet is received, we notify the user about that tweet via desktop notifications.  If you use Chrome and Gmail, you’ve probably seen notifications pop up when you receive a new mail, or about an upcoming appointment.  The Desktop Notifications API allows script to request a small toast to appear on the desktop of the user. The contents of the toast can either be specified as iconUrl/title/text strings, or as a URL to a remote resource which contains the contents of the toast. They’re part of the WebAPI spec, though still in early draft, and so far only implemented in Chrome.

Since notifications are still something that may change, in Chrome, they’re under the webkit vendor prefix. The Chromium implementation details can be found on the Chromium site.

There are two types of notifications that you can surface, what I’ll call simple notifications like the one on the left that contains an image, a headline and some text; and HTML notifications.  HTML notifications are much more powerful because instead of sending the image, headline and text, you send it a URL, and it will display the contents of that URL. That means you can style your notification to match your applications UI and even provide interactivity through links, javascript, etc.

Let’s have a look at how we implemented new tweet notifications in TweeterFall.

Requesting Permission

When the user turns on notifications in TweeterFall, we need to check to see if we have permission to show notifications and if we don’t we need to ask for it. Desktop Notifications pop open a new window and could quickly become an easy spam target if we didn’t provide some kind of permission system. When a user grants or denies permission, they do so for the domain.

function requestPermissionIfRequired() {
  if (!hasNotificationPermission() && (window.webkitNotifications)) {
    window.webkitNotifications.requestPermission();
  }
}

In requestPermissionIfRequired(), we check to see if the browser supports notifications, and if we already have permission. If we don’t have permission, but notifications are supported, Chrome prompts the user asking for permission to provide notifications.

Checking For Permission

I also use feature detection here to check to see if notifications are supported in the browser.  We will only return true if notifications are supported and the user has already given permission to show notifications.

function hasNotificationPermission() {
  return !!(window.webkitNotifications)
    && (window.webkitNotifications.checkPermission() == 0);
}

Show the Notification

We’re finally ready to show our notification to the user.

function showNotification(pic, title, text) {
  if (hasNotificationPermission()) {
    var notificationWindow =
      window.webkitNotifications.createNotification(pic, title, text);
    notificationWindow.show();
    setTimeout(function(notWin) {
      notWin.cancel();
    }, 10000, notificationWindow);
  } else {
    console.log("showNotification: No Permission");
  }
}

The method takes three parameters, the image to use, the title and the text do display. When the function is called, it checks to see if it has permission using our previously established permission check that also encompasses feature detection, so we know we’ll be safe calling this even if the browser doesn’t support notifications. We then create the notification with window.webkitNotifications.createNotification(pic, title, text) and show it with .show().

Immediately below that, we have a setTimeout call with an anonymous function that takes one parameter (the window handle to the notification) and will hide the notification after the timeout period has expired. If we didn’t do this, the notification would remain on screen until the user clicked the close button, or closed the browser. I’ve hard coded the timeout to be 10000ms, and you can change this to suit your needs.

Summary

As you can see, showing notifications is pretty easy, check & ask for permission, and then show the notification. You can use these to let the user know when a task is complete, when there’s new information that they might be interested in and so forth. Obviously these only work when a user is on your site, but you can overcome that limitation by publishing your app in the Chrome Web Store and using background pages.

Other Resources

Be sure to check out the notification tutorial on HTML5Rocks, it covers some additional things like the events that notifications fire and using HTML notifications.

HTML5_Logo_512.png

Building TweeterFall

0

I want to devote some time to how Ernest and I built TweeterFall, the demo that we used for our I/O Boot Camp presentation last week.  Instead of showing isolated demos of some of the new HTML5 features, we wanted to show how you might use some of these things in real world scenarios, and thus we built TweeterFall.  TweeterFall is a Twitter visualizer that grabs the tweets from a list and shows them in a kind of waterfall like visualization.

What did we use to build TweeterFall?

HTML5 Semantics

We used the new HTML5 semantic elements as our primary markup elements.  The bar across the top is a <header>, the area on the left where the tweets fall is a <section>, the list of Tweets on the right and the configuration panel are both <aside>’s, and the individual tweets are <article>s.

GeoLocation

Since we figured that most people would be in SF, it’d be fun to use some kind of geo location to show how close the person was to your current position when they made their Tweet.  To do that, we pull the users current location using navigator.geolocation.watchPosition and compare that to the geo data in the tweet.  We used the GeoLocation tutorial from HTML5Rocks.com as our inspiration for this.

IndexedDB

Twitter only allows you to pull about 20 tweets at a time, so if you want to see anything before that, you either need to log in (and we didn’t really want to deal with OAuth for this presentation) or store the previous tweets locally.  IndexedDB was the perfect solution for this, as it allows you to store significant quantities of structured data.  Figuring out IndexedDB was probably my biggest challenge for this project, mostly because it’s pretty new and there isn’t a lot of documentation on it yet.  That and the fact that everything you do is asynchronous, so you need lots of fun callbacks!  Look for a whole post on this one shortly.

LocalStorage

IndexedDB works great for structured data, but we also wanted a place to store simple configuration data, so we used web storage for that.  I wanted to have my configuration data in an object so I could easily access things, so I used JSON to parse and stringify my config object as it went into and came out of local storage.

Notifications

We used simple notifications to alert users when there were new tweets, and also included a special handler so that they would disappear after a couple of seconds and not remain on screen for too long.  One of the updates I want to make here is to use HTML notifications, so that we can make things look a little more like the rest of the UI.

Speech Input

Okay, so this one was a bit gratuitous. :)  If you add x-webkit-speech to text inputs, Chrome will add a microphone icon to the text box.  When the user clicks on the icon, Chrome turns on the microphone, listens for input and then converts what you said into text!  No JavaScript, just all magic!

Web Workers

Web Workers are great for handing off complex or intensive processes off to another thread so they don’t block the rest of the browser. In our case, we wanted to isolate the requests we were making to Twitter into their own place and allow them to “push” notifications to us.  One thing to remember with Web Workers, is that they run off in their own separate thread, and don’t have access to the DOM or most other window objects (which means jQuery won’t work either).

Canvas

This was a bit gratuitous too, we’ve got a fun update in mind for this that is equally gratuitous, but much prettier.

2D Transforms

When you mouse over the list of Tweets on the right, they get bigger to make them easier to read.  To do this, we use the :hover pseudo selector and apply a scale transform to make it bigger, and then a translation to ensure it appears in the right place.  If we didn’t do the translation, part of the tweet would get cut off on the right side.

CSS Animations

When I first created the TweeterFall visualization, I was using CSS transitions and transforms to animate the tweet as it fell down, but then realized that the better way to do it would be CSS Animations and transforms.

Web Fonts

We used Web Fonts to spice up the look of the page somewhat, there are lots of great fonts available.  Typically I’ll either grab fonts from Font Squirrel or Google’s Web Fonts.  As Ernest presented the section on web fonts, he said something to the effect of developers have had only 8 fonts they can count on across multiple platforms/browsers, and because Comic Sans doesn’t really count, they only really had 7.

Chrome Developer Tools

So we didn’t really use this in the app, but let me say this, the JavaScript console was the biggest life saver as we were debugging and trying to understand everything that was going on.  Not only did we use a few console.log’s, we also were able to execute code and do all kinds of other great things.  In fact, Paul Irish blogged about the I/O session he did with Pavel Feldman, and linked to the video - definitely worth checking out!

Up next, a dive into each of these!

bootcamp_logo

I/O Boot Camp – Getting Started With HTML5

0

Last week at I/O Boot Camp, Ernest Delgado and I presented a session titled Getting Started with HTML5.

Rather than take folks through the laundry list cool new stuff that’s part of HTML5, we took them through a smaller subset of features, showing how we used them in a real world application – TweeterFall.

You can watch the video, check out the slides and try TweeterFall.  When trying the slides, be sure to try them in Chrome dev, Canary or Safari to get the full 3D effects!

Go to Top