Chrome & HTML5
Awesome!
Building^Applications for the Web
Me
Who is this guy anyhow?
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-widthmax-heightmin-widthmin-heightmax-device-pixel-ratiomin-device-pixel-ratioorientation:portraitorientation: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!