Chrome & HTML5

Awesome!

Building^Applications for the Web

Me

Who is this guy anyhow?

+Bob Aman

Code Labs

What we're going to build together!

Offline Applications

When you're traveling, it's useful to have ticket information handy. But you might not have internet access at the airport! We'll be building a web application that can store ticket information and operate while offline.

Multi-Device Designs

It's great that we can build an offline web application, but who really wants to pull out their laptop at the airport ticket counter? It'd be way better if you could use your mobile phone to check your ticket information.

Packaging for Chrome Web Store

Great! And now that we've made our application work well while offline and on mobile devices, now we want people to be able to discover it. We should package it up and publish it on the Chrome Web Store!

Before We Start

What you're going to need!

g-africa-codelab.appspot.com

Download the code lab here!

Offline Apps

Let's take this offline.

The most important feature of an offline application is the cache manifest. It tells the browser what to cache for offline use.

<html manifest="example.appcache">
  ...
</html>

It's simply an external resource you specify with an attribute on your <html> element.

Offline Apps

Let's take this offline.

A manifest resource has three sections, and looks like this:

CACHE MANIFEST
CACHE:
# Cached files go here
FALLBACK:
# Replacements to serve when offline
NETWORK:
# Resources that require online access

Offline Apps

Let's take this offline.

A manifest should be served with a Content-Type of text/cache-manifest.

Most web servers will try to serve it as application/octet-stream or text/plain and that may cause problems in some browsers.

Offline Apps

Let's take this offline.

The application cache can be accessed programmatically via the window.applicationCache variable.

To force an update:
window.applicationCache.update()
To check status:
window.applicationCache.status
To handle events:
window.applicationCache.addEventListener()

Offline Apps

Let's take this offline.

The application cache may fire a number of different events:

  • "cached"
  • "checking"
  • "downloading"
  • "error"
  • "noupdate"
  • "obsolete"
  • "progress"
  • "updateready"

Offline Apps

Let's take this offline.

To respond to any of those events:

window.applicationCache.addEventListener(
  "updateready", function(event) {
    // Your code goes here
  }
);

Offline Apps

Let's take this offline.

Some tips:

  • You may want to disable any server-side caching of the manifest while debugging.
  • Put a version number in a comment at the top of the manifest. Incrementing will force a refresh.
  • Consider logging most application cache events to the console when debugging. Some logging happens automatically in Chrome.
  • Don't bother trying to test from file:/// URIs. It doesn't work.

Offline Apps

Let's take this offline.

Exercise #1

Figure out which resources this application will need to cache for offline usage.

Tip: Check Chrome's network tab in the developer tools to see what requests are being made. Generally you'll need to cache anything the browser is requesting.

Offline Apps

Let's take this offline.

The application cache manifest does a great job handling static data. But you may need to handle dynamic data while offline.

That's what IndexedDB is for!

Offline Apps

Let's take this offline.

The first thing you'll notice about IndexedDB is that there's no SQL. So we'll refer to it as a "NoSQL" database.

Instead of using SQL, you query an index, obtain a cursor, and iterate on the result set.

An index is a structure that speeds up the retrieval of data retrieval operations.

A cursor is a control structure that enables traversal over a database result. They are the database equivalent of iterators.

The result set is the collection of objects that matched a particular query.

Offline Apps

Let's take this offline.

Currently only Chrome and Firefox have implemented IndexedDB, however, most of the major browser vendors have indicated an intention to support it.

Today, it's supported via vendor prefixes. Let's simplify this:

window.indexedDB =
  window.indexedDB ||
  window.webkitIndexedDB ||
  window.mozIndexedDB;

Offline Apps

Let's take this offline.

We'll be using an asynchronous API to access our IndexedDB database. That means there's lots of callbacks. For instance, here's how to open a new database:

var request = indexedDB.open(
  "tickets", "Ticket data within."
);
request.onsuccess = function(e) {
  var database = e.target.result;
};

Offline Apps

Let's take this offline.

IndexedDB has a versioned schema. You can only make changes to the schema within a setVersion request.

var version = "1.0";
var migrationRequest = database.setVersion(version);
migrationRequest.onsuccess = function(e) {
  // Schema changes are done here.
};

Offline Apps

Let's take this offline.

Instead of tables, IndexedDB has object stores. Let's create one for airline tickets. Remember, this can only be done within a setVersion request.

var ticketStore = database.createObjectStore("ticket",
  {keyPath: "ticketNumber"}
);

Offline Apps

Let's take this offline.

Similar to a primary key, IndexedDB needs a keyPath that tells it what value on each object to use as the object's unique lookup key.

{
  "ticketNumber": "1234567890",
  "confirmation": "ABCDEF",
  "airline": "Worldwide Airways"
}

Offline Apps

Let's take this offline.

Exercise #2

You need to create an object store named 'ticket' with a keyPath of ticketNumber.

Offline Apps

Let's take this offline.

While we're within a setVersion request, we can also create an index on the confirmation value:

ticketStore.createIndex(
  "confirmation", "confirmation", {unique: true}
);

Offline Apps

Let's take this offline.

Exercise #3

The ticketNumber field will be unique because it's the primary key, but confirmations are unique as well. Add an index that will ensure the uniqueness of the confirmation.

Offline Apps

Let's take this offline.

Before we can query the database, we're going to need to add something for us to query. And before we can do that, we need to create a transaction.

var transaction = database.transaction(
  ["ticket"],
  IDBTransaction.READ_WRITE
);

Offline Apps

Let's take this offline.

Now that we have our transaction, lets make a request to put our ticket into the object store.

var request = transaction.objectStore("ticket").put({
  "ticketNumber": "1234567890",
  "confirmation": "QRXTFC",
  "airline": "Worldwide Airways"
});

Unlike SQL databases, you can use put to both create and update data.

Offline Apps

Let's take this offline.

Like all other IndexedDB asynchronous requests, we can set onsuccess and/or onfailure callbacks.

request.onsuccess = function(e) {
  // Great our ticket was stored in the database!
}
request.onfailure = function(e) {
  // Ooops, something's gone horribly wrong!
}

Offline Apps

Let's take this offline.

Exercise #4

Take the ticket object and add it to the database.

Offline Apps

Let's take this offline.

OK, great! Now let's try to get our ticket back out again, shall we? Make a new transaction, just like before, and then:

var ticketStore = transaction.objectStore("ticket");
// Get everything in the object store;
var keyRange = IDBKeyRange.lowerBound("");
var cursorRequest = ticketStore.openCursor(keyRange);

Offline Apps

Let's take this offline.

That last one seemed a bit scary. But it's not so bad, now we have a cursor that we can use to iterate over the tickets. This callback is called once per result.

cursorRequest.onsuccess = function(e) {
  var result = e.target.result;
  if(!!result == false) return;
  var ticket = result.value;
  // Do something with it!
  result.continue();
};

Offline Apps

Let's take this offline.

Exercise #5

Remove the example ticket boilerplate from the init method, and instead, query the database for the ticket data. Use the supplied ticketr.buildTicketElement function to generate each ticket DOM structure.

Offline Apps

Let's take this offline.

Alright, but let's say we don't want that ticket anymore, how do we get rid of it? Turns out it's super, super easy!

var ticketNumber = "1234567890";
var ticketStore = transaction.objectStore("ticket");
var deleteRequest = ticketStore.delete(ticketNumber);

And then just like we've been doing all along, write some callbacks!

Mobile Apps

Look Mom! No wires!

Great, now that our app works offline, let's think about making it work on phones and other devices!

Mobile Apps

Look Mom! No wires!

Most smartphones don't attempt to squeeze an entire web page onto the screen all at once. Instead they have a viewport, which is a bit like reading a newspaper through a magnifying glass.

This works great for pages that aren't designed for mobile, but we want to make our design look great on phones! This code snippet will constrain the viewport to match the device size:

<meta name="viewport"
      content="width=device-width,initial-scale=1.0" />

Mobile Apps

Look Mom! No wires!

Exercise #6

Constrain your viewport to match the device size!

Mobile Apps

Look Mom! No wires!

Great, now we can start making our app look good on smaller devices! CSS3 allows use specify that certain CSS rules should only apply to device configurations with certain properties.

Device height and width are the properties we're going to care about most.

Mobile Apps

Look Mom! No wires!

A good device width to target is 480px. This will typically cover most smartphones and other devices with very constrained screen sizes.

@media screen and (max-device-width: 480px) {
  /* CSS rules for smartphones go here. */
}

Mobile Apps

Look Mom! No wires!

You can also try querying on:

  • max-width
  • max-height
  • min-width
  • min-height
  • max-device-pixel-ratio
  • min-device-pixel-ratio
  • orientation:portrait
  • orientation:landscape

Note that not all devices support all properties. Testing is recommended.

Mobile Apps

Look Mom! No wires!

CSS Tricks has a great list of media queries for common devices:

http://goo.gl/87dRo

Mobile Apps

Look Mom! No wires!

Exercise #7

Our current layout is really optimized for desktops and maybe tablets.

Adjust the UI to work on smartphones by adding CSS rules to move the edit section into a modal view when there isn't enough screen area to support a side-by-side layout.

Chrome Apps

Distribution is key.

Great, so now we've got an application that can work offline, and it looks nice on mobile phones, now let's get it into the Chrome Web Store!

Chrome Apps

Distribution is key.

For most apps, you can get them packaged up for distribution in a matter of a minute or two. Just go to appmator.appspot.com!

Because we're using the application cache, you may want to request the "unlimitedStorage" permission. Just paste it in manually to the manifest.json file.

Chrome Apps

Distribution is key.

Exercise #8

Package up your application! Be sure you're requesting unlimited storage!

Then install it into your local Chrome browser as an unpacked application! You can do this from the chrome://extensions page.

We're Done

That's a wrap!

Well, we sure learned a lot there. Quick recap:

First, we figured out how to cache all our static resources so they could be used while offline. Then we learned how to use IndexedDB to store dynamic data on the client.

Then we learned how to build layouts that adapt to multiple device sizes.

And finally, we found out how to package up our application for the Chrome Web Store.

We're Done

That's a wrap!

The End

Not really. Just the beginning, because now you get to go build awesome applications for the web!