Bug 10240: Offline circulation using HTML5 and IndexedDB
[koha.git] / koha-tmpl / intranet-tmpl / lib / jquery / plugins / jquery.indexeddb.js
1 (function($, undefined) {
2         var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
3         var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
4         var IDBCursor = window.IDBCursor || window.webkitIDBCursor;
5         IDBCursor.PREV = IDBCursor.PREV || "prev";
6         IDBCursor.NEXT = IDBCursor.NEXT || "next";
7
8         /**
9          * Best to use the constant IDBTransaction since older version support numeric types while the latest spec
10          * supports strings
11          */
12         var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
13
14         function getDefaultTransaction(mode) {
15                 var result = null;
16                 switch (mode) {
17                         case 0:
18                         case 1:
19                         case "readwrite":
20                         case "readonly":
21                                 result = mode;
22                                 break;
23                         default:
24                                 result = IDBTransaction.READ_WRITE || "readwrite";
25                 }
26                 return result;
27         }
28
29         $.extend({
30                 /**
31                  * The IndexedDB object used to open databases
32                  * @param {Object} dbName - name of the database
33                  * @param {Object} config - version, onupgradeneeded, onversionchange, schema
34                  */
35                 "indexedDB": function(dbName, config) {
36                         if (config) {
37                                 // Parse the config argument
38                                 if (typeof config === "number") config = {
39                                         "version": config
40                                 };
41
42                                 var version = config.version;
43                                 if (config.schema && !version) {
44                                         var max = -1;
45                                         for (key in config.schema) {
46                                                 max = max > key ? max : key;
47                                         }
48                                         version = config.version || max;
49                                 }
50                         }
51
52
53                         var wrap = {
54                                 "request": function(req, args) {
55                                         return $.Deferred(function(dfd) {
56                                                 try {
57                                                         var idbRequest = typeof req === "function" ? req(args) : req;
58                                                         idbRequest.onsuccess = function(e) {
59
60                                                                 dfd.resolveWith(idbRequest, [idbRequest.result, e]);
61                                                         };
62                                                         idbRequest.onerror = function(e) {
63
64                                                                 dfd.rejectWith(idbRequest, [idbRequest.error, e]);
65                                                         };
66                                                         if (typeof idbRequest.onblocked !== "undefined" && idbRequest.onblocked === null) {
67                                                                 idbRequest.onblocked = function(e) {
68
69                                                                         var res;
70                                                                         try {
71                                                                                 res = idbRequest.result;
72                                                                         } catch (e) {
73                                                                                 res = null; // Required for Older Chrome versions, accessing result causes error
74                                                                         }
75                                                                         dfd.notifyWith(idbRequest, [res, e]);
76                                                                 };
77                                                         }
78                                                         if (typeof idbRequest.onupgradeneeded !== "undefined" && idbRequest.onupgradeneeded === null) {
79                                                                 idbRequest.onupgradeneeded = function(e) {
80
81                                                                         dfd.notifyWith(idbRequest, [idbRequest.result, e]);
82                                                                 };
83                                                         }
84                                                 } catch (e) {
85                                                         e.name = "exception";
86                                                         dfd.rejectWith(idbRequest, ["exception", e]);
87                                                 }
88                                         });
89                                 },
90                                 // Wraps the IDBTransaction to return promises, and other dependent methods
91                                 "transaction": function(idbTransaction) {
92                                         return {
93                                                 "objectStore": function(storeName) {
94                                                         try {
95                                                                 return wrap.objectStore(idbTransaction.objectStore(storeName));
96                                                         } catch (e) {
97                                                                 idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
98                                                                 return wrap.objectStore(null);
99                                                         }
100                                                 },
101                                                 "createObjectStore": function(storeName, storeParams) {
102                                                         try {
103                                                                 return wrap.objectStore(idbTransaction.db.createObjectStore(storeName, storeParams));
104                                                         } catch (e) {
105                                                                 idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
106                                                         }
107                                                 },
108                                                 "deleteObjectStore": function(storeName) {
109                                                         try {
110                                                                 idbTransaction.db.deleteObjectStore(storeName);
111                                                         } catch (e) {
112                                                                 idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
113                                                         }
114                                                 },
115                                                 "abort": function() {
116                                                         idbTransaction.abort();
117                                                 }
118                                         };
119                                 },
120                                 "objectStore": function(idbObjectStore) {
121                                         var result = {};
122                                         // Define CRUD operations
123                                         var crudOps = ["add", "put", "get", "delete", "clear", "count"];
124                                         for (var i = 0; i < crudOps.length; i++) {
125                                                 result[crudOps[i]] = (function(op) {
126                                                         return function() {
127                                                                 return wrap.request(function(args) {
128                                                                         return idbObjectStore[op].apply(idbObjectStore, args);
129                                                                 }, arguments);
130                                                         };
131                                                 })(crudOps[i]);
132                                         }
133
134                                         result.each = function(callback, range, direction) {
135                                                 return wrap.cursor(function() {
136                                                         if (direction) {
137                                                                 return idbObjectStore.openCursor(wrap.range(range), direction);
138                                                         } else {
139                                                                 return idbObjectStore.openCursor(wrap.range(range));
140                                                         }
141                                                 }, callback);
142                                         };
143
144                                         result.index = function(name) {
145                                                 return wrap.index(function() {
146                                                         return idbObjectStore.index(name);
147                                                 });
148                                         };
149
150                                         result.createIndex = function(prop, options, indexName) {
151                                                 if (arguments.length === 2 && typeof options === "string") {
152                                                         indexName = arguments[1];
153                                                         options = null;
154                                                 }
155                                                 if (!indexName) {
156                                                         indexName = prop;
157                                                 }
158                                                 return wrap.index(function() {
159                                                         return idbObjectStore.createIndex(indexName, prop, options);
160                                                 });
161                                         };
162
163                                         result.deleteIndex = function(indexName) {
164                                                 return idbObjectStore.deleteIndex(indexName);
165                                         };
166
167                                         return result;
168                                 },
169
170                                 "range": function(r) {
171                                         if ($.isArray(r)) {
172                                                 if (r.length === 1) {
173                                                         return IDBKeyRange.only(r[0]);
174                                                 } else {
175                                                         return IDBKeyRange.bound(r[0], r[1], (typeof r[2] === 'undefined') ? true : r[2], (typeof r[3] === 'undefined') ? true : r[3]);
176                                                 }
177                                         } else if (typeof r === "undefined") {
178                                                 return null;
179                                         } else {
180                                                 return r;
181                                         }
182                                 },
183
184                                 "cursor": function(idbCursor, callback) {
185                                         return $.Deferred(function(dfd) {
186                                                 try {
187
188                                                         var cursorReq = typeof idbCursor === "function" ? idbCursor() : idbCursor;
189                                                         cursorReq.onsuccess = function(e) {
190
191                                                                 if (!cursorReq.result) {
192                                                                         dfd.resolveWith(cursorReq, [null, e]);
193                                                                         return;
194                                                                 }
195                                                                 var elem = {
196                                                                         // Delete, update do not move
197                                                                         "delete": function() {
198                                                                                 return wrap.request(function() {
199                                                                                         return cursorReq.result["delete"]();
200                                                                                 });
201                                                                         },
202                                                                         "update": function(data) {
203                                                                                 return wrap.request(function() {
204                                                                                         return cursorReq.result["update"](data);
205                                                                                 });
206                                                                         },
207                                                                         "next": function(key) {
208                                                                                 this.data = key;
209                                                                         },
210                                                                         "key": cursorReq.result.key,
211                                                                         "value": cursorReq.result.value
212                                                                 };
213
214                                                                 dfd.notifyWith(cursorReq, [elem, e]);
215                                                                 var result = callback.apply(cursorReq, [elem]);
216
217                                                                 try {
218                                                                         if (result === false) {
219                                                                                 dfd.resolveWith(cursorReq, [null, e]);
220                                                                         } else if (typeof result === "number") {
221                                                                                 cursorReq.result["advance"].apply(cursorReq.result, [result]);
222                                                                         } else {
223                                                                                 if (elem.data) cursorReq.result["continue"].apply(cursorReq.result, [elem.data]);
224                                                                                 else cursorReq.result["continue"]();
225                                                                         }
226                                                                 } catch (e) {
227
228                                                                         dfd.rejectWith(cursorReq, [cursorReq.result, e]);
229                                                                 }
230                                                         };
231                                                         cursorReq.onerror = function(e) {
232
233                                                                 dfd.rejectWith(cursorReq, [cursorReq.result, e]);
234                                                         };
235                                                 } catch (e) {
236
237                                                         e.type = "exception";
238                                                         dfd.rejectWith(cursorReq, [null, e]);
239                                                 }
240                                         });
241                                 },
242
243                                 "index": function(index) {
244                                         try {
245                                                 var idbIndex = (typeof index === "function" ? index() : index);
246                                         } catch (e) {
247                                                 idbIndex = null;
248                                         }
249
250                                         return {
251                                                 "each": function(callback, range, direction) {
252                                                         return wrap.cursor(function() {
253                                                                 if (direction) {
254                                                                         return idbIndex.openCursor(wrap.range(range), direction);
255                                                                 } else {
256                                                                         return idbIndex.openCursor(wrap.range(range));
257                                                                 }
258
259                                                         }, callback);
260                                                 },
261                                                 "eachKey": function(callback, range, direction) {
262                                                         return wrap.cursor(function() {
263                                                                 if (direction) {
264                                                                         return idbIndex.openKeyCursor(wrap.range(range), direction);
265                                                                 } else {
266                                                                         return idbIndex.openKeyCursor(wrap.range(range));
267                                                                 }
268                                                         }, callback);
269                                                 },
270                                                 "get": function(key) {
271                                                         if (typeof idbIndex.get === "function") {
272                                                                 return wrap.request(idbIndex.get(key));
273                                                         } else {
274                                                                 return idbIndex.openCursor(wrap.range(key));
275                                                         }
276                                                 },
277                                                 "count": function() {
278                                                         if (typeof idbIndex.count === "function") {
279                                                                 return wrap.request(idbIndex.count());
280                                                         } else {
281                                                                 throw "Count not implemented for cursors";
282                                                         }
283                                                 },
284                                                 "getKey": function(key) {
285                                                         if (typeof idbIndex.getKey === "function") {
286                                                                 return wrap.request(idbIndex.getKey(key));
287                                                         } else {
288                                                                 return idbIndex.openKeyCursor(wrap.range(key));
289                                                         }
290                                                 }
291                                         };
292                                 }
293                         };
294
295
296                         // Start with opening the database
297                         var dbPromise = wrap.request(function() {
298
299                                 return version ? indexedDB.open(dbName, parseInt(version)) : indexedDB.open(dbName);
300                         });
301                         dbPromise.then(function(db, e) {
302
303                                 db.onversionchange = function() {
304                                         // Try to automatically close the database if there is a version change request
305                                         if (!(config && config.onversionchange && config.onversionchange() !== false)) {
306                                                 db.close();
307                                         }
308                                 };
309                         }, function(error, e) {
310
311                                 // Nothing much to do if an error occurs
312                         }, function(db, e) {
313                                 if (e && e.type === "upgradeneeded") {
314                                         if (config && config.schema) {
315                                                 // Assuming that version is always an integer
316
317                                                 for (var i = e.oldVersion + 1; i <= e.newVersion; i++) {
318                                                         typeof config.schema[i] === "function" && config.schema[i].call(this, wrap.transaction(this.transaction));
319                                                 }
320                                         }
321                                         if (config && typeof config.upgrade === "function") {
322                                                 config.upgrade.call(this, wrap.transaction(this.transaction));
323                                         }
324                                 }
325                         });
326
327                         return $.extend(dbPromise, {
328                                 "cmp": function(key1, key2) {
329                                         return indexedDB.cmp(key1, key2);
330                                 },
331                                 "deleteDatabase": function() {
332                                         // Kinda looks ugly coz DB is opened before it needs to be deleted.
333                                         // Blame it on the API
334                                         return $.Deferred(function(dfd) {
335                                                 dbPromise.then(function(db, e) {
336                                                         db.close();
337                                                         wrap.request(function() {
338                                                                 return indexedDB.deleteDatabase(dbName);
339                                                         }).then(function(result, e) {
340                                                                 dfd.resolveWith(this, [result, e]);
341                                                         }, function(error, e) {
342                                                                 dfd.rejectWith(this, [error, e]);
343                                                         }, function(db, e) {
344                                                                 dfd.notifyWith(this, [db, e]);
345                                                         });
346                                                 }, function(error, e) {
347                                                         dfd.rejectWith(this, [error, e]);
348                                                 }, function(db, e) {
349                                                         dfd.notifyWith(this, [db, e]);
350                                                 });
351                                         });
352                                 },
353                                 "transaction": function(storeNames, mode) {
354                                         !$.isArray(storeNames) && (storeNames = [storeNames]);
355                                         mode = getDefaultTransaction(mode);
356                                         return $.Deferred(function(dfd) {
357                                                 dbPromise.then(function(db, e) {
358                                                         var idbTransaction;
359                                                         try {
360
361                                                                 idbTransaction = db.transaction(storeNames, mode);
362
363                                                                 idbTransaction.onabort = idbTransaction.onerror = function(e) {
364                                                                         dfd.rejectWith(idbTransaction, [e]);
365                                                                 };
366                                                                 idbTransaction.oncomplete = function(e) {
367                                                                         dfd.resolveWith(idbTransaction, [e]);
368                                                                 };
369                                                         } catch (e) {
370
371                                                                 e.type = "exception";
372                                                                 dfd.rejectWith(this, [e]);
373                                                                 return;
374                                                         }
375                                                         try {
376                                                                 dfd.notifyWith(idbTransaction, [wrap.transaction(idbTransaction)]);
377                                                         } catch (e) {
378                                                                 e.type = "exception";
379                                                                 dfd.rejectWith(this, [e]);
380                                                         }
381                                                 }, function(err, e) {
382                                                         dfd.rejectWith(this, [e, err]);
383                                                 }, function(res, e) {
384
385                                                         //dfd.notifyWith(this, ["", e]);
386                                                 });
387
388                                         });
389                                 },
390                                 "objectStore": function(storeName, mode) {
391                                         var me = this,
392                                                 result = {};
393
394                                         function op(callback) {
395                                                 return $.Deferred(function(dfd) {
396                                                         function onTransactionProgress(trans, callback) {
397                                                                 try {
398
399                                                                         callback(trans.objectStore(storeName)).then(function(result, e) {
400                                                                                 dfd.resolveWith(this, [result, e]);
401                                                                         }, function(err, e) {
402                                                                                 dfd.rejectWith(this, [err, e]);
403                                                                         });
404                                                                 } catch (e) {
405
406                                                                         e.name = "exception";
407                                                                         dfd.rejectWith(trans, [e, e]);
408                                                                 }
409                                                         }
410                                                         me.transaction(storeName, getDefaultTransaction(mode)).then(function() {
411
412                                                                 // Nothing to do when transaction is complete
413                                                         }, function(err, e) {
414                                                                 // If transaction fails, CrudOp fails
415                                                                 if (err.code === err.NOT_FOUND_ERR && (mode === true || typeof mode === "object")) {
416
417                                                                         var db = this.result;
418                                                                         db.close();
419                                                                         dbPromise = wrap.request(function() {
420
421                                                                                 return indexedDB.open(dbName, (parseInt(db.version, 10) || 1) + 1);
422                                                                         });
423                                                                         dbPromise.then(function(db, e) {
424
425                                                                                 db.onversionchange = function() {
426                                                                                         // Try to automatically close the database if there is a version change request
427                                                                                         if (!(config && config.onversionchange && config.onversionchange() !== false)) {
428                                                                                                 db.close();
429                                                                                         }
430                                                                                 };
431                                                                                 me.transaction(storeName, getDefaultTransaction(mode)).then(function() {
432
433                                                                                         // Nothing much to do
434                                                                                 }, function(err, e) {
435                                                                                         dfd.rejectWith(this, [err, e]);
436                                                                                 }, function(trans, e) {
437
438                                                                                         onTransactionProgress(trans, callback);
439                                                                                 });
440                                                                         }, function(err, e) {
441                                                                                 dfd.rejectWith(this, [err, e]);
442                                                                         }, function(db, e) {
443                                                                                 if (e.type === "upgradeneeded") {
444                                                                                         try {
445
446                                                                                                 db.createObjectStore(storeName, mode === true ? {
447                                                                                                         "autoIncrement": true
448                                                                                                 } : mode);
449
450                                                                                         } catch (ex) {
451
452                                                                                                 dfd.rejectWith(this, [ex, e]);
453                                                                                         }
454                                                                                 }
455                                                                         });
456                                                                 } else {
457                                                                         dfd.rejectWith(this, [err, e]);
458                                                                 }
459                                                         }, function(trans) {
460
461                                                                 onTransactionProgress(trans, callback);
462                                                         });
463                                                 });
464                                         }
465
466                                         function crudOp(opName, args) {
467                                                 return op(function(wrappedObjectStore) {
468                                                         return wrappedObjectStore[opName].apply(wrappedObjectStore, args);
469                                                 });
470                                         }
471
472                                         function indexOp(opName, indexName, args) {
473                                                 return op(function(wrappedObjectStore) {
474                                                         var index = wrappedObjectStore.index(indexName);
475                                                         return index[opName].apply(index[opName], args);
476                                                 });
477                                         }
478
479                                         var crud = ["add", "delete", "get", "put", "clear", "count", "each"];
480                                         for (var i = 0; i < crud.length; i++) {
481                                                 result[crud[i]] = (function(op) {
482                                                         return function() {
483                                                                 return crudOp(op, arguments);
484                                                         };
485                                                 })(crud[i]);
486                                         }
487
488                                         result.index = function(indexName) {
489                                                 return {
490                                                         "each": function(callback, range, direction) {
491                                                                 return indexOp("each", indexName, [callback, range, direction]);
492                                                         },
493                                                         "eachKey": function(callback, range, direction) {
494                                                                 return indexOp("eachKey", indexName, [callback, range, direction]);
495                                                         },
496                                                         "get": function(key) {
497                                                                 return indexOp("get", indexName, [key]);
498                                                         },
499                                                         "count": function() {
500                                                                 return indexOp("count", indexName, []);
501                                                         },
502                                                         "getKey": function(key) {
503                                                                 return indexOp("getKey", indexName, [key]);
504                                                         }
505                                                 };
506                                         };
507
508                                         return result;
509                                 }
510                         });
511                 }
512         });
513
514         $.indexedDB.IDBCursor = IDBCursor;
515         $.indexedDB.IDBTransaction = IDBTransaction;
516         $.idb = $.indexedDB;
517 })(jQuery);