appcache/promises/indexeddb presentation 2014-09-20

25
Appcache, Promises, and IndexedDB examn is a web application for online testing. One day we decided it needed a way to work offline, too. This is a story about three of the tools that helped it get there.

Upload: jasewell

Post on 30-Jun-2015

262 views

Category:

Technology


0 download

DESCRIPTION

A brief overview of HTML Application Cache, Javascript Promises, and IndexedDB as they relate to the creation of an offline testing interface for an online testing web application. Some code samples and links to further reading.

TRANSCRIPT

Page 1: Appcache/Promises/IndexedDB presentation 2014-09-20

Appcache, Promises, and IndexedDB

examn is a web application for online testing. One day we decided it needed a way to work offline, too. This is a story about three of the tools that helped it get there.

Page 2: Appcache/Promises/IndexedDB presentation 2014-09-20

Offline Testing Project Requirements

As much as possible, the interface must function offline.● Authenticate users.● Download tests to be taken later.● Upload responses for grading.● Enforce time limits and security codes for tests

that have them.● Be compatible with existing test questions.● Don’t expose questions to people not currently

taking a test.● Don’t expose answers at all.

Page 3: Appcache/Promises/IndexedDB presentation 2014-09-20

The Tools We Chose

● Offline Page Loading: HTML5 Appcacheo Basically the only way to point a browser at a

webpage while offline and have it load.● Offline Data Storage: IndexedDB

o With WebSQL deprecated, this looks like the future of client-side databases.

● General Humanity: Promiseso IndexedDB is powerful but not user-friendly. I

made a wrapper for it, and the wrapper uses Promises.

Page 4: Appcache/Promises/IndexedDB presentation 2014-09-20

Appcache Manifest

<html manifest=”path/to/manifest.file”>

Simple Sample

Page 5: Appcache/Promises/IndexedDB presentation 2014-09-20

Appcache Life Cycle

Load page from cache.

Request manifest.

Update cache.

Request manifest.

Set status, fire event.

applicationCache.update();

(Listeners?)

Zzzz….

Page 6: Appcache/Promises/IndexedDB presentation 2014-09-20

● The cached version of every file will be loaded first whether you’re online or not. You have to provide a way for the page to update itself when there are changes.

● You have to list every file. Every script, every stylesheet, every image, everything.o Even the ones you don’t want to make

available offline. (That’s what the NETWORK section is for.)

● Fallback resources don’t know why you got sent there.

● There’s no way to tell the browser which files need updating. Any manifest change == full cache download.

Manifest Gotchas

Page 7: Appcache/Promises/IndexedDB presentation 2014-09-20

● Manifests can be generated on the fly. (Content-Type: text/cache-manifest)

● Use a version number or date in a comment to force cache updates.

● Any page that references the manifest is implicitly included.● Putting * in NETWORK reverses the default-deny behavior of

Appcache wrt non-cached files.● View your cached files at chrome://appcache-internals or

about:cache.

Manifest Tricks

Manifest Gotchas 2: Gotcha Harder● Don’t list the manifest in the manifest.● Don’t use wildcards in the CACHE section.● Server-side caching schemes that rely on changing urls (query

strings, etc) must be reflected in the manifest.● Manifests that change during load are treated as an error, so

don’t just put a timestamp in a comment.

Page 8: Appcache/Promises/IndexedDB presentation 2014-09-20

Keeping The App Up to Date

● Check applicationCache.status on ready.o applicationCache.UPDATEREADY >>

need to reload.● Set an updateready listener.● Periodically call

applicationCache.update().● On updateready, inform the user there’s

an update and let them decide when to reload.o No modals!

● Also looks for .OBSOLETE.

Page 9: Appcache/Promises/IndexedDB presentation 2014-09-20

examn.offline’s manifest.php

● Loop over a hardcoded array of file paths to be included.o Check file modification times.o CSS files are parsed looking for url() paths,

which are added to the end of the array.● Include the most recent file

modification time in the output as a comment.

(It’s also cached, based on the modify time of the site’s root folder, so we don’t have to do all of this every time.)

Page 10: Appcache/Promises/IndexedDB presentation 2014-09-20

JavaScript Promises

Old and Busted:Request/Listener

New Hotness:Promises

Function returns a request object, listeners are added to that object.

Function returns a Promise object, listeners are added via method chaining.

Discussion question: Is the “Old and Busted”/”New Hotness” categorization scheme itself now Old and/or Busted? If so, what is the new New Hotness?

Page 11: Appcache/Promises/IndexedDB presentation 2014-09-20

Promises Versus Requests

Request/Listener● Events can fire many times.● If the event has already

happened, the listener will not fire at time of assignment.

● Not chainable.● Arbitrarily long list of

fireable events.

Promise● Promises resolve only once.● Already-resolved promises

call the appropriate functions immediately.

● Chainable.● Outcomes limited to resolve

and reject.

Page 12: Appcache/Promises/IndexedDB presentation 2014-09-20

Promise Chaining

If first() returns a Promise, the rest of the chain will be applied to it, as if you had writtenfirst().then(second)....

Otherwise second() will be called immediately with the return value of first().

Catch calls will receive any Promise rejections or uncaught exceptions from any upstream call.

promiseRequest(params).then(first).then(second).catch(err1).then(third).catch(err2).then(finally);

Page 13: Appcache/Promises/IndexedDB presentation 2014-09-20

Basic Promise Creation

By convention, the parameter given to reject() should always be an Error.

Page 14: Appcache/Promises/IndexedDB presentation 2014-09-20

Advanced Promise Topics

● Let pa be an array of Promises.o Promise.all(pa) rejects when any of pa

rejects, or resolves when all of pa have resolved.

o Promise.race(pa) resolves or rejects with the first of pa to resolve or reject.

● Promise.resolve(a) and Promise.reject(a) return Promises that always resolve or reject with a.o If a is a thenable, Promise.resolve(a)

instead casts it to a Promise.● getPromise().then(yay,boo) ==

getPromise().then(yay).catch(boo)

Page 15: Appcache/Promises/IndexedDB presentation 2014-09-20

IndexedDB Vocabulary

Asynchronous. Object-oriented. Transactional. Key/Value.

domain

database

objectStore

key

key

key

key

object

object

object

object

index

key

key

key

key

value

value

value

value

cursors

ranges

Page 16: Appcache/Promises/IndexedDB presentation 2014-09-20

IndexedDB Strengths/Weaknesses

IndexedDB is good at:● Polymorphism● Transactions● Error handling● Queries that can

be expressed as a range on an index

IndexedDB is bad at*:● Encouraging

readable code● Joins● Other kinds of

queries

*: completely incapable of

Page 17: Appcache/Promises/IndexedDB presentation 2014-09-20

IndexedDB Creation ExampleAside from success and failure, indexedDB.open() can fire an upgradeneeded event when the requested version is higher than the existing version (or there is no existing version).The only place you can create or modify objectStores is inside an onupgradeneeded listener.

Page 18: Appcache/Promises/IndexedDB presentation 2014-09-20

IndexedDB Transaction Examples

Page 19: Appcache/Promises/IndexedDB presentation 2014-09-20

IndexedDB Cursor/Range Example

To operate on an index other than the primary key:var index = objectStore.index(indexName);index.openCursor();

Other IDBKeyRange types:● lowerBound()● upperBound()● only()

Page 20: Appcache/Promises/IndexedDB presentation 2014-09-20

DevTools and IndexedDB Data

This is the IndexedDB section of Chrome’s Resources tab.

The tree shows databases, objectStores, and indices.

(Extensions are available to view IndexedDB data in Firefox/Firebug.)

Page 21: Appcache/Promises/IndexedDB presentation 2014-09-20

The ALLOFEindexedDB Wrapper

Why use a wrapper?● Do I have to answer

that? You saw that code.

Okay, then why make your own wrapper?● Needed multiple

objectStore support.● Wanted to provide

some Ext-like methods, e.g. each(), getBy().

Methods● add● addBatch● each● get● getBy● put● putBatch● remove● removeBy

Page 22: Appcache/Promises/IndexedDB presentation 2014-09-20

Examples 2: ALLOFEindexedDB

All of the previous IndexedDB examples redone with ALLOFEindexedDB. 65 lines -> 32 lines.

Page 23: Appcache/Promises/IndexedDB presentation 2014-09-20

The examn.offline Schema

App DB● Users

o key: user_ido index: username

(unique)

● Fileso key: test_queue_id_patho index: test_queue_ido index: filename

Report DB(one per user)

● Test Queueso key: test_queue_id

● Test HTMLo key: test_queue_ido index: test_ido index: test_version_id

● Test Responseso key:

queue_test_questiono index: queue_test_pageo index: test_queue_ido index: submitted

Page 24: Appcache/Promises/IndexedDB presentation 2014-09-20

What about Compatibility?

For this project we had the luxury of not having to worry about that, but here’s where you can use these things:● Appcache

o IE 10+, FF 3.5+, Chrome 4+, Safari 4+, Opera 10.6+

● Promises (native)o IE no, FF 29+, Chrome 33+, Safari 8+, Opera

20+o There is a polyfill at promisejs.org.

● IndexedDBo IE 10/11 partial, FF 16+, Chrome 24+, Safari

8+, Opera 15+

(compatibility data from caniuse.com.)

Page 25: Appcache/Promises/IndexedDB presentation 2014-09-20

Further Reading

● Appcacheo html5rocks.com/en/tutorials/appcache/beginner/o alistapart.com/article/application-cache-is-a-douchebag

● Promiseso promisejs.orgo html5rocks.com/en/tutorials/es6/promises/

● IndexedDBo developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API