TheDJ has uploaded a new change for review. https://gerrit.wikimedia.org/r/157818
Change subject: Add a jQuery promise lib around indexedDB ...................................................................... Add a jQuery promise lib around indexedDB Added ed5507cd49ee93fbdec449341306b81507db8f41 of https://github.com/axemclion/jquery-indexeddb Will use this to improve change: I38372e4ae38 Change-Id: I9b5ccc018ad19ae3abcc95e879e9c4a467533d71 --- M resources/Resources.php A resources/lib/jquery/jquery.indexeddb.js 2 files changed, 548 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/18/157818/1 diff --git a/resources/Resources.php b/resources/Resources.php index 3073152..827ef21 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -249,6 +249,9 @@ 'jquery.hoverIntent' => array( 'scripts' => 'resources/lib/jquery/jquery.hoverIntent.js', ), + 'jquery.indexeddb' => array( + 'scripts' => 'resources/lib/jquery/jquery.indexeddb.js', + }, 'jquery.json' => array( // @deprecated since 1.24: Use the 'json' module and global JSON object instead. 'scripts' => array( diff --git a/resources/lib/jquery/jquery.indexeddb.js b/resources/lib/jquery/jquery.indexeddb.js new file mode 100644 index 0000000..0001ed6 --- /dev/null +++ b/resources/lib/jquery/jquery.indexeddb.js @@ -0,0 +1,545 @@ +/* + * Copyright 2012 Parashuram N and other contributors + * http://nparashuram.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +(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); -- To view, visit https://gerrit.wikimedia.org/r/157818 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I9b5ccc018ad19ae3abcc95e879e9c4a467533d71 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: TheDJ <hartman.w...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits