summaryrefslogtreecommitdiffstats
path: root/LayoutTests/storage/indexeddb/tutorial.html
blob: 2e7e41fcf1679df07870c45bcf2926921e7bf211 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
<html><title>IndexedDB Tutorial</title>
<script>

// This is a tutorial that highlights many of the features of IndexedDB along witha number of
// caveats that currently exist in Chromium/WebKit but which will hopefully be improved upon
// over time.
//
// The latest version of the spec can be found here:
// http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html but note that there are quite a
// few bugs currently opened against it and some major unresolved issues (like whether dynamic
// transactions should be in for v1). Many of the bugs are filed here:
// http://www.w3.org/Bugs/Public/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&component=Indexed+Database+API&longdesc_type=allwordssubstr&longdesc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&status_whiteboard_type=allwordssubstr&status_whiteboard=&keywords_type=allwords&keywords=&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailtype1=substring&email1=&emailtype2=substring&email2=&bug_id_type=anyexact&bug_id=&votes=&chfieldfrom=&chfieldto=Now&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&known_name=IndexedDB&query_based_on=IndexedDB&field0-0-0=noop&type0-0-0=noop&value0-0-0=
// Discussion happens on public-webapps@w3.org
//
// Although not user friendly, additional capabilities and example code can be found in the
// tests for IndexedDB which are here:
// http://trac.webkit.org/browser/trunk/LayoutTests/storage/indexeddb
//
// This document is currently maintained by Jeremy Orlow <jorlow@chromium.org>


// This is not an ideal layout test since it doesn't verify things as thoroughly as it could,
// but adding such content would make it much more cluttered and thus wouldn't serve its primary
// goal of teaching people IndexedDB. That said, it does have a good amount of coverage and
// serves as a living document describing what's expected to work and how within WebKit so it
// seems well worth having checked in.
if (window.layoutTestController)
    layoutTestController.dumpAsText(true);


function setup()
{
    // As this API is still experimental, it's being shipped behind vendor specific prefixes.
    if ('webkitIndexedDB' in window) {
        indexedDB = webkitIndexedDB;
        IDBCursor = webkitIDBCursor;
        IDBKeyRange = webkitIDBKeyRange;
        IDBTransaction = webkitIDBTransaction;
    }

    // This tutorial assumes that Mozilla and WebKit match each other which isn't true at the
    // moment, but we can hope it'll become true over time.
    if ('moz_indexedDB' in window) {
        indexedDB = moz_indexedDB;
        // Not implemented by them yet. I'm just guessing what they'll be.
        IDBCursor = moz_IDBCursor;
        IDBKeyRange = moz_IDBKeyRange;
        IDBTransaction = moz_IDBTransaction;
    }
}

function log(txt)
{
    document.write(txt + "<br>");
}

function logError(txt)
{
    log("<font color=red>" + txt + "</font>");
}

function start()
{
    setup();

    // This is an example of one of the many asynchronous commands in IndexedDB's async interface.
    // Each returns an IDBRequest object which has "success" and "error" event handlers. You can use
    // "addEventListener" if you'd like, but I'm using the simpler = syntax. Only one or the other
    // will fire. You're guaranteed that they won't fire until control is returned from JavaScript
    // execution.
    var request = indexedDB.open("myDB", "This is a description of the database."); // Chromium/WebKit doesn't yet require the description, but it will soon.
    request.onsuccess = onOpen;
    request.onerror = unexpectedError;
}

function unexpectedError()
{
    // If an asynchronous call results in an error, an "error" event will fire on the IDBRequest
    // object that was returned and the event's code and message attributes will be populated with
    // the correct values.
    logError("Error " + event.code + ": " + event.message);

    // Unfortunately, Chromium/WebKit do not implicitly abort a transaction when an error occurs
    // within one of its async operations. In the future, when an error occurs and the event is
    // not canceled, the transaction will be aborted.
    if (currentTransaction)
        currentTransaction.abort();
}

function onOpen()
{
    // If an asynchronous call results in success, a "success" event will fire on the IDBRequest
    // object that was returned and the call's result will be placed in the event's "result"
    // attribute. In some cases, the expected result will be null.
    window.db = event.result;

    // The IDBDatabase object has a "version" attribute. This can only be set by calling
    // "setVersion" on the database and supplying a new version. This also starts a new
    // transaction which is very special. There are many details and gotchas surrounding
    // setVersion which we'll get into later.
    if (db.version == "1.0") {
        // We could skip setting up the object stores and indexes if this were a real application
        // that wasn't going to change things without changing the version number. But since this
        // is both a tutorial and a living document, we'll go on and set things up every time we run.
    }
    var request = db.setVersion("1.0");
    request.onsuccess = onSetVersion;
    request.onerror = unexpectedError;
}

function onSetVersion()
{
    // We are now in a setVersion transaction. Such a transaction is the only place where one
    // can add or remove indexes and objectStores. The result (property of event) is an
    // IDBTransaction object that has "complete", "abort", and "timeout" event handlers which tell
    // us when the transaction has committed, aborted, or timed out.
    window.currentTransaction = event.result;
    currentTransaction.oncomplete = onSetVersionComplete;
    currentTransaction.onabort = unexpectedAbort;

    // Delete existing object stores.
    while (db.objectStores.length)
        db.removeObjectStore(db.objectStores[0]);

    // Now that we have a blank slate, let's create an objectStore. An objectStore is simply an
    // ordered mapping of keys to values. We can iterate through ranges of keys or do individual
    // lookups. ObjectStores don't have any schema.
    //
    // Keys can be integers, strings, or null. (The spec also defines dates and there's talk of
    // handling arrays, but these are not implemented yet in Chromium/WebKit.) Values can be
    // anything supported by the structured clone algorithm
    // (http://dev.w3.org/html5/spec/Overview.html#internal-structured-cloning-algorithm) which
    // is a superset of what can be expressed in JSON. (Note that Chromium/WebKit does not fully
    // implement the structured clone algorithm yet, but it definitely handles anything JSON
    // serializable.)
    //
    // There are two types of objectStores: ones where the path is supplied manually every time a
    // value is inserted and those with a "key path". A keyPath is essentially a JavaScript
    // expression that is evaluated on every value to extract a key. For example, if you pass in
    // the value of "{fname: 'john', lname: 'doe', address: {street: 'Buckingham Palace", number:
    // 76}, siblings: ["Nancy", "Marcus"], id: 22}" and an objectStore has a keyPath of "id" then
    // 22 will be the key for this value. In objectStores, each key must be unique.
    //
    // Note that the exact syntax allowed for keyPaths is not yet well specified, but
    // Chromium/WebKit currently allows paths that are multiple levels deep within an object and
    // allows that to be intermixed with array dereferences. So, for example, a key path of
    // "address.number" or "siblings[0]" would be legal (provided every entry had an address with
    // a number attribute and at least one sibling). You can even go wild and say
    // "foo[0][2].bar[0].baz.test[1][2][3]". It's possible this will change in the future though.
    //
    // If you set autoIncrement (the third optional parameter), IndexedDB will generate a key
    // for your entry automatically. And if you have a keyPath set, it'll set the value at
    // the location of the keyPath _in the database_ (i.e. it will not modify the value you pass
    // in to put/add). Unfortunately autoIncrement is not yet implemented in Chromium/WebKit.
    //
    // Let's now create an objectStore for people. We'll supply a key path in this case.
    var objectStore = db.createObjectStore("people", "id");

    // Notice that it returned synchronously. The rule of thumb is that any call that touches (in
    // any way) keys or values is asynchronous and any other call (besides setVersion and open) are
    // asynchronous.
    //
    // Now let's create some indexes. Indexes allow you to create other keys via key paths which
    // will also point to a particular value in an objectStore. In this example, we'll create
    // indexes for a persons first and last name. Indexes can optionally be specified to not be
    // unique, which is good in the case of names. The first parameter is the name of the index.
    // Second is the key path. The third specifies uniqueness.
    var fname = objectStore.createIndex("fname", "fname", false);
    var lname = objectStore.createIndex("lname", "lname", false);

    // Note that if you wanted to delete these indexes, you can either call objectStore.removeIndex
    // or simply delete the objectStores that own the indexes. (Note that removeObjectStore and
    // removeIndex may be changed to deleteObjectStore and deleteIndex in the future.)
    //
    // If we wanted to, we could populate the objectStore with some data here or do anything else
    // allowed in a normal (i.e. non-setVersion) transaction. This is useful so that data migrations
    // can be atomic with changes to the objectStores/indexes.
    //    
    // Because we haven't actually made any new asynchronous requests, this transaction will
    // start committing as soon as we leave this function. This will cause oncomplete event handler
    // for the transaction will fire shortly after. IndexedDB transactions commit whenever control is
    // returned from JavaScript with no further work being queued up against the transaction. This
    // means one cannot call setTimeout, do an XHR, or anything like that and expect my transaction
    // to still be around when that completes.
    
}

function unexpectedAbort()
{
    logError("A transaction aborted unexpectedly!");
}

function onSetVersionComplete()
{
    // Lets create a new transaction and then not schedule any work on it to watch it abort itself.
    // Transactions (besides those created with setVersion) are created synchronously. All three
    // parameters are optional for transaction.
    //
    // The first specifies which object stores to lock. The spec specifies "dynamic transactions"
    // which don't require this and which have finer grained locks, but no one yet supports this and
    // it may even be dropped from the first version of the spec, so I don't suggest you rely on it.
    // Chromium/WebKit also does not yet support anything finer grained than database level locking,
    // so in this tutorial we'll just pass in the empty array which means "lock the world".
    //
    // The second parameter specifies the locking mode. The default is READ_ONLY (i.e. a shared lock).
    // That's fine for this case, but later we'll ask for IDBTransaction.READ_WRITE. At the moment,
    // Chromium/WebKit pretends every transaction is READ_WRITE, which is kind of bad. (Note that
    // SNAPSHOT_READ will soon be removed from the spec.)
    //
    // The last parameter is the timeout length. At the moment, Chromium/WebKit defaults to 0 which
    // means never, but it's possible we'll change this in the future, so set it if you really care.
    window.currentTransaction = db.transaction([], IDBTransaction.READ_WRITE, 0);
    currentTransaction.oncomplete = unexpectedComplete;
    currentTransaction.onabort = onTransactionAborted;

    // Verify that "people" is the only object store in existance. The objectStores attribute is
    // a DOMStringList which is somewhat like an array.
    var objectStoreList = db.objectStores;
    if (objectStoreList.length != 1
        || !objectStoreList.contains("people")
        || objectStoreList.item(0) != "people"
        || objectStoreList[0] != "people") {
        logError("Something went wrong.");
    }

    // Let's grab a handle to the objectStore. This handle is tied to the transaction that creates
    // it and thus becomes invalid once this transaction completes.
    var objectStore = currentTransaction.objectStore("people");
    if (!objectStore)
        logError("Something went wrong.");

    // If we try to grab an objectStore that doesn't exist, IndexedDB throws an exception.
    try {
        currentTransaction.objectStore("x");
        logError("Something went wrong.");
    } catch (e) {
        // Note that the error messages in exceptions are mostly lies at the moment. The reason is
        // that the spec re-uses exception codes for existing exceptions and there's no way we can
        // disambiguate between the two. The best work-around at the moment is to look at
        // http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#the-idbdatabaseexception-interface
        // to figure out what the number corresponds to. We will try to resolve this soon in spec-land.
    }

    // Verify that fname and lname are the only indexes in existance.
    if (objectStore.indexNames.length != 2)
        logError("Something went wrong.");

    // Note that no async actions were ever queued up agianst our transaction, so it'll abort once
    // we leave this context.
}

function unexpectedComplete()
{
    logError("A transaction committed unexpectedly!");
}

function onTransactionAborted()
{
    // Now let's make a real transaction and a person to our objectStore.
    window.currentTransaction = db.transaction(["people"], IDBTransaction.READ_WRITE, 0);
    currentTransaction.onabort = unexpectedAbort;

    var people = currentTransaction.objectStore("people");
    var request = people.put({fname: 'John', lname: 'Doe', id: 1}); // If our objectStore didn't have a key path, the second parameter would have been the key.
    request.onsuccess = onPutSuccess;
    request.onerror = unexpectedError;

    // While we're at it, why not add a few more? Multiple queued up async commands will be executed
    // sequentially (though there is talk of prioritizing cursor.continue--see discussion below). Since
    // we don't care about the individual commands' successes, we'll only bother with on error handlers.
    //
    // Remember that our implementation of unexpectedError should abort the "currentTransaction" in the
    // case of an error. (Though no error should occur in this case.)
    people.put({fname: 'Jane', lname: 'Doe', id: 2}).onerror = unexpectedError;
    people.put({fname: 'Philip', lname: 'Fry', id: 3}).onerror = unexpectedError;

    // Not shown here are the .remove method (which is soon to be renamed .delete) and .add (which is
    // like .put except that it fires an onerror if the element already exists).
}

function onPutSuccess()
{
    // Result is the key used for the put.
    if (event.result !== 1)
        logError("Something went wrong.");

    // We should be able to request the transaction via event.transaction from within any event handler
    // (like this one) but this is not yet implemented in Chromium/WebKit. As a work-around, we use the
    // global "currentTransaction" variable we set above.
    currentTransaction.oncomplete = onPutTransactionComplete;
}

function onPutTransactionComplete()
{
    // OK, now let's query the people objectStore in a couple different ways. First up, let's try get.
    // It simply takes in a key and returns a request whose result will be the value.
    window.currentTransaction = db.transaction(["people"], IDBTransaction.READ_WRITE, 0);
    currentTransaction.onabort = unexpectedAbort;

    var people = currentTransaction.objectStore("people");
    var request = people.get(1);
    request.onsuccess = onGetSuccess;
    request.onerror = unexpectedError;

    // Note that multiple objectStore (or index) method calls will return different objects (that still
    // refer to the same objectStore/index on disk).
    people.someProperty = true;
    if (currentTransaction.objectStore("people").someProperty)
        logError("Something went wrong.");
}

function onGetSuccess()
{
    if (event.result.fname !== "John")
        logError("Something went wrong.");

    // Events have a .source attribute which is the object that fired the event. In this case, it's our
    // "people" objectStore object.
    var people = event.source;

    // Now let's try opening a cursor from id 1 (exclusive/open) to id 3 (inclusive/closed). This means
    // we'll get the objects for ids 2 and 3. You can also create cursors that are only right or only
    // left bounded or ommit the bound in order to grab all objects. You can also specify a direction
    // which can be IDBCursor.NEXT (default) for the cursor to move forward, NEXT_NO_DUPLICATE to only
    // return unique entires (only applies to indexes with unique set to false), PREV to move backwards,
    // and PREV_NO_DUPLICATE.
    var keyRange = IDBKeyRange.bound(1, 3, true, false);
    var request = people.openCursor(keyRange, IDBCursor.NEXT);
    request.onsuccess = onObjectStoreCursor;
    request.onerror = unexpectedError;
}

function onObjectStoreCursor()
{
    // The result of openCursor is an IDBCursor object or null if there are no (more--see below)
    // records left.
    var cursor = event.result;
    if (cursor === null) {
        cursorComplete(event.source); // The soruce is still an objectStore.
        return;
    }

    // We could use these values if we wanted to.
    var key = cursor.key;
    var value = cursor.value;

    // cursor.count is probably going to be removed.
    // cursor.update and .remove are not yet implemented in Chromium/WebKit.

    // cursor.continue will reuse the same request object as what openCursor returned. In the future,
    // we MAY prioritize .continue() calls ahead of all other async operations queued up. This will
    // introduce a level of non-determinism but should speed things up. Mozilla has already implemented
    // this non-standard behavior, from what I've head.
    event.result.continue();
}

function cursorComplete(objectStore)
{
    // While still in the same transaction, let's now do a lookup on the lname index.
    var lname = objectStore.index("lname");

    // Note that the spec has not been updated yet, but instead of get and getObject, we now
    // have getKey and get. The former returns the objectStore's key that corresponds to the key
    // in the index. get returns the objectStore's value that corresponds to the key in the
    // index.
    var request = lname.getKey("Doe");
    request.onsuccess = onIndexGetSuccess;
    request.onerror = unexpectedError;
}

function onIndexGetSuccess()
{
    // Because we did "getKey" the result is the objectStore's key.
    if (event.result !== 1)
        logError("Something went wrong.");

    // Similarly, indexes have openCursor and openKeyCursor. We'll try a few of them with various
    // different IDBKeyRanges just to demonstrate how to use them, but we won't bother to handle
    // the onsuccess conditions.
    var lname = event.source;
    lname.openCursor(IDBKeyRange.leftBound("Doe", false), IDBCursor.NEXT_NO_DUPLICATE);
    lname.openCursor(null, IDBCursor.PREV_NO_DUPLICATE);
    lname.openCursor(IDBKeyRange.rightBound("ZZZZ"));
    lname.openCursor(IDBKeyRange.only("Doe"), IDBCursor.PREV);
    lname.openCursor();
    lname.openKeyCursor();

    // We should be able to request the transaction via event.transaction from within any event handler
    // (like this one) but this is not yet implemented in Chromium/WebKit. As a work-around, we use the
    // global "currentTransaction" variable we set above.
    currentTransaction.oncomplete = onAllDone;
}

function onAllDone()
{
    log("Everything worked!");
}

// The way setVersion is supposed to work:
//   To keep things simple to begin with, objectStores and indexes can only be created in a setVersion
// transaction and one can only run if no other connections are open to the database. This is designed
// to save app developers from having an older verison of a web page that expects a certain set of
// objectStores and indexes from breaking in odd ways when things get changed out from underneith it.
// In the future, we'll probably add a more advanced mechanism, but this is it for now.
//   Because a setVersion transaction could stall out nearly forever until the user closes windows,
// we've added a "blocked" event to the request object returned by setVersion. This will fire if the
// setVersion transaction can't begin because other windows have an open connection. The app can then
// either pop something up telling the user to close windows or it can tell the other windows to call
// .close() on their database handle. .close() halts any new transactions from starting and waits for
// the existing ones to finish. It then closes the connection and any indexedDB calls afterwards are
// invalid (they'll probably throw, but this isn't specified yet). We may specify .close() to return
// an IDBRequest object so that we can fire the onsuccess when the close completes.
//   Once inside a setVersion transaction, you can do anything you'd like. The one connection which
// was allowed to stay open to complete the setVersion transaction will stay alive. Multiple
// setVersion transactions can be queued up at once and will fire in the order queued (though
// this obviously only works if they're queued in the same page).
//
// The current status of setVersion in Chromium/WebKit:
//   In Chromium/WebKit we currently don't enforce the "all connections must be closed before a
// setVersion transaction starts" rule. We also don't implement database.close() or have a blocked
// event on the request .setVersion() returns.
//
// The current status of workers:
//   Chromium/WebKit do not yet support workers using IndexedDB. Support for the async interface
// will likely come before the sync interface. For now, a work-around is using postMessage to tell
// the page what to do on the worker's behalf in an ad-hoc manner. Anything that can be serialized
// to disk can be serialized for postMessage.

</script>
<body onload="start()">
Please view source for more information on what this is doing and why...<br><br>
</body>
</html>