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
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);
|