You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

491 lines
24 KiB

(function ($, undefined) {
'use strict';
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
var IDBCursor = window.IDBCursor || window.webkitIDBCursor || {};
if (typeof IDBCursor.PREV === "undefined") {
IDBCursor.PREV = "prev";
}
if (typeof IDBCursor.NEXT === "undefined") {
IDBCursor.NEXT = "next";
}
/**
* Best to use the constant IDBTransaction since older version support numeric types while the latest spec
* supports strings
*/
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
function getDefaultTransaction(mode) {
var result = null;
switch (mode) {
case 0:
case 1:
case "readwrite":
case "readonly":
result = mode;
break;
default:
result = IDBTransaction.READ_WRITE || "readwrite";
}
return result;
}
$.extend({
/**
* The IndexedDB object used to open databases
* @param {Object} dbName - name of the database
* @param {Object} config - version, onupgradeneeded, onversionchange, schema
*/
"indexedDB": function (dbName, config) {
if (config) {
// Parse the config argument
if (typeof config === "number") config = {
"version": config
};
var version = config.version;
if (config.schema && !version) {
var max = -1;
for (var key in config.schema) {
max = max > key ? max : key;
}
version = config.version || max;
}
}
var wrap = {
"request": function (req, args) {
return $.Deferred(function (dfd) {
try {
var idbRequest = typeof req === "function" ? req(args) : req;
idbRequest.onsuccess = function (e) {
dfd.resolveWith(idbRequest, [idbRequest.result, e]);
};
idbRequest.onerror = function (e) {
dfd.rejectWith(idbRequest, [idbRequest.error, e]);
};
if (typeof idbRequest.onblocked !== "undefined" && idbRequest.onblocked === null) {
idbRequest.onblocked = function (e) {
var res;
try {
res = idbRequest.result;
} catch (e) {
res = null; // Required for Older Chrome versions, accessing result causes error
}
dfd.notifyWith(idbRequest, [res, e]);
};
}
if (typeof idbRequest.onupgradeneeded !== "undefined" && idbRequest.onupgradeneeded === null) {
idbRequest.onupgradeneeded = function (e) {
dfd.notifyWith(idbRequest, [idbRequest.result, e]);
};
}
} catch (e) {
e.name = "exception";
dfd.rejectWith(idbRequest, ["exception", e]);
}
});
},
// Wraps the IDBTransaction to return promises, and other dependent methods
"transaction": function (idbTransaction) {
return {
"objectStore": function (storeName) {
try {
return wrap.objectStore(idbTransaction.objectStore(storeName));
} catch (e) {
idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
return wrap.objectStore(null);
}
},
"createObjectStore": function (storeName, storeParams) {
try {
return wrap.objectStore(idbTransaction.db.createObjectStore(storeName, storeParams));
} catch (e) {
idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
}
},
"deleteObjectStore": function (storeName) {
try {
idbTransaction.db.deleteObjectStore(storeName);
} catch (e) {
idbTransaction.readyState !== idbTransaction.DONE && idbTransaction.abort();
}
},
"abort": function () {
idbTransaction.abort();
}
};
},
"objectStore": function (idbObjectStore) {
var result = {};
// Define CRUD operations
var crudOps = ["add", "put", "get", "delete", "clear", "count"];
for (var i = 0; i < crudOps.length; i++) {
result[crudOps[i]] = (function (op) {
return function () {
return wrap.request(function (args) {
return idbObjectStore[op].apply(idbObjectStore, args);
}, arguments);
};
})(crudOps[i]);
}
result.each = function (callback, range, direction) {
return wrap.cursor(function () {
if (direction) {
return idbObjectStore.openCursor(wrap.range(range), direction);
} else {
return idbObjectStore.openCursor(wrap.range(range));
}
}, callback);
};
result.index = function (name) {
return wrap.index(function () {
return idbObjectStore.index(name);
});
};
result.createIndex = function (prop, options, indexName) {
if (arguments.length === 2 && typeof options === "string") {
indexName = arguments[1];
options = null;
}
if (!indexName) {
indexName = prop;
}
return wrap.index(function () {
return idbObjectStore.createIndex(indexName, prop, options);
});
};
result.deleteIndex = function (indexName) {
return idbObjectStore.deleteIndex(indexName);
};
return result;
},
"range": function (r) {
if ($.isArray(r)) {
if (r.length === 1) {
return IDBKeyRange.only(r[0]);
} else {
return IDBKeyRange.bound(r[0], r[1], (typeof r[2] === 'undefined') ? false : r[2], (typeof r[3] === 'undefined') ? false : r[3]);
}
} else if (typeof r === "undefined") {
return null;
} else {
return r;
}
},
"cursor": function (idbCursor, callback) {
return $.Deferred(function (dfd) {
try {
var cursorReq = typeof idbCursor === "function" ? idbCursor() : idbCursor;
cursorReq.onsuccess = function (e) {
if (!cursorReq.result) {
dfd.resolveWith(cursorReq, [null, e]);
return;
}
var elem = {
// Delete, update do not move
"delete": function () {
return wrap.request(function () {
return cursorReq.result["delete"]();
});
},
"update": function (data) {
return wrap.request(function () {
return cursorReq.result["update"](data);
});
},
"next": function (key) {
this.data = key;
},
"key": cursorReq.result.key,
"value": cursorReq.result.value
};
dfd.notifyWith(cursorReq, [elem, e]);
var result = callback.apply(cursorReq, [elem]);
try {
if (result === false) {
dfd.resolveWith(cursorReq, [null, e]);
} else if (typeof result === "number") {
cursorReq.result["advance"].apply(cursorReq.result, [result]);
} else {
if (elem.data) cursorReq.result["continue"].apply(cursorReq.result, [elem.data]);
else cursorReq.result["continue"]();
}
} catch (e) {
dfd.rejectWith(cursorReq, [cursorReq.result, e]);
}
};
cursorReq.onerror = function (e) {
dfd.rejectWith(cursorReq, [cursorReq.result, e]);
};
} catch (e) {
e.type = "exception";
dfd.rejectWith(cursorReq, [null, e]);
}
});
},
"index": function (index) {
try {
var idbIndex = (typeof index === "function" ? index() : index);
} catch (e) {
idbIndex = null;
}
return {
"each": function (callback, range, direction) {
return wrap.cursor(function () {
if (direction) {
return idbIndex.openCursor(wrap.range(range), direction);
} else {
return idbIndex.openCursor(wrap.range(range));
}
}, callback);
},
"eachKey": function (callback, range, direction) {
return wrap.cursor(function () {
if (direction) {
return idbIndex.openKeyCursor(wrap.range(range), direction);
} else {
return idbIndex.openKeyCursor(wrap.range(range));
}
}, callback);
},
"get": function (key) {
if (typeof idbIndex.get === "function") {
return wrap.request(idbIndex.get(key));
} else {
return idbIndex.openCursor(wrap.range(key));
}
},
"count": function () {
if (typeof idbIndex.count === "function") {
return wrap.request(idbIndex.count());
} else {
throw "Count not implemented for cursors";
}
},
"getKey": function (key) {
if (typeof idbIndex.getKey === "function") {
return wrap.request(idbIndex.getKey(key));
} else {
return idbIndex.openKeyCursor(wrap.range(key));
}
}
};
}
};
// Start with opening the database
var dbPromise = wrap.request(function () {
return version ? indexedDB.open(dbName, parseInt(version)) : indexedDB.open(dbName);
});
dbPromise.then(function (db, e) {
db.onversionchange = function () {
// Try to automatically close the database if there is a version change request
if (!(config && config.onversionchange && config.onversionchange() !== false)) {
db.close();
}
};
}, function (error, e) {
// Nothing much to do if an error occurs
}, function (db, e) {
if (e && e.type === "upgradeneeded") {
if (config && config.schema) {
// Assuming that version is always an integer
for (var i = e.oldVersion + 1; i <= e.newVersion; i++) {
typeof config.schema[i] === "function" && config.schema[i].call(this, wrap.transaction(this.transaction));
}
}
if (config && typeof config.upgrade === "function") {
config.upgrade.call(this, wrap.transaction(this.transaction));
}
}
});
return $.extend(dbPromise, {
"cmp": function (key1, key2) {
return indexedDB.cmp(key1, key2);
},
"deleteDatabase": function () {
// Kinda looks ugly coz DB is opened before it needs to be deleted.
// Blame it on the API
return $.Deferred(function (dfd) {
dbPromise.then(function (db, e) {
db.close();
wrap.request(function () {
return indexedDB.deleteDatabase(dbName);
}).then(function (result, e) {
dfd.resolveWith(this, [result, e]);
}, function (error, e) {
dfd.rejectWith(this, [error, e]);
}, function (db, e) {
dfd.notifyWith(this, [db, e]);
});
}, function (error, e) {
dfd.rejectWith(this, [error, e]);
}, function (db, e) {
dfd.notifyWith(this, [db, e]);
});
});
},
"transaction": function (storeNames, mode) {
!$.isArray(storeNames) && (storeNames = [storeNames]);
mode = getDefaultTransaction(mode);
return $.Deferred(function (dfd) {
dbPromise.then(function (db, e) {
var idbTransaction;
try {
idbTransaction = db.transaction(storeNames, mode);
idbTransaction.onabort = idbTransaction.onerror = function (e) {
dfd.rejectWith(idbTransaction, [e]);
};
idbTransaction.oncomplete = function (e) {
dfd.resolveWith(idbTransaction, [e]);
};
} catch (e) {
e.type = "exception";
dfd.rejectWith(this, [e]);
return;
}
try {
dfd.notifyWith(idbTransaction, [wrap.transaction(idbTransaction)]);
} catch (e) {
e.type = "exception";
dfd.rejectWith(this, [e]);
}
}, function (err, e) {
dfd.rejectWith(this, [e, err]);
}, function (res, e) {
//dfd.notifyWith(this, ["", e]);
});
});
},
"objectStore": function (storeName, mode) {
var me = this,
result = {};
function op(callback) {
return $.Deferred(function (dfd) {
function onTransactionProgress(trans, callback) {
try {
callback(trans.objectStore(storeName)).then(function (result, e) {
dfd.resolveWith(this, [result, e]);
}, function (err, e) {
dfd.rejectWith(this, [err, e]);
});
} catch (e) {
e.name = "exception";
dfd.rejectWith(trans, [e, e]);
}
}
me.transaction(storeName, getDefaultTransaction(mode)).then(function () {
// Nothing to do when transaction is complete
}, function (err, e) {
// If transaction fails, CrudOp fails
if (err.code === err.NOT_FOUND_ERR && (mode === true || typeof mode === "object")) {
var db = this.result;
db.close();
dbPromise = wrap.request(function () {
return indexedDB.open(dbName, (parseInt(db.version, 10) || 1) + 1);
});
dbPromise.then(function (db, e) {
db.onversionchange = function () {
// Try to automatically close the database if there is a version change request
if (!(config && config.onversionchange && config.onversionchange() !== false)) {
db.close();
}
};
me.transaction(storeName, getDefaultTransaction(mode)).then(function () {
// Nothing much to do
}, function (err, e) {
dfd.rejectWith(this, [err, e]);
}, function (trans, e) {
onTransactionProgress(trans, callback);
});
}, function (err, e) {
dfd.rejectWith(this, [err, e]);
}, function (db, e) {
if (e.type === "upgradeneeded") {
try {
db.createObjectStore(storeName, mode === true ? {
"autoIncrement": true
} : mode);
} catch (ex) {
dfd.rejectWith(this, [ex, e]);
}
}
});
} else {
dfd.rejectWith(this, [err, e]);
}
}, function (trans) {
onTransactionProgress(trans, callback);
});
});
}
function crudOp(opName, args) {
return op(function (wrappedObjectStore) {
return wrappedObjectStore[opName].apply(wrappedObjectStore, args);
});
}
function indexOp(opName, indexName, args) {
return op(function (wrappedObjectStore) {
var index = wrappedObjectStore.index(indexName);
return index[opName].apply(index[opName], args);
});
}
var crud = ["add", "delete", "get", "put", "clear", "count", "each"];
for (var i = 0; i < crud.length; i++) {
result[crud[i]] = (function (op) {
return function () {
return crudOp(op, arguments);
};
})(crud[i]);
}
result.index = function (indexName) {
return {
"each": function (callback, range, direction) {
return indexOp("each", indexName, [callback, range, direction]);
},
"eachKey": function (callback, range, direction) {
return indexOp("eachKey", indexName, [callback, range, direction]);
},
"get": function (key) {
return indexOp("get", indexName, [key]);
},
"count": function () {
return indexOp("count", indexName, []);
},
"getKey": function (key) {
return indexOp("getKey", indexName, [key]);
}
};
};
return result;
}
});
}
});
$.indexedDB.IDBCursor = IDBCursor;
$.indexedDB.IDBTransaction = IDBTransaction;
$.idb = $.indexedDB;
})(jQuery);