Skip to content

Instantly share code, notes, and snippets.

@lakenen
Last active August 29, 2015 14:02
Show Gist options
  • Save lakenen/4b37253a21a3d112cef8 to your computer and use it in GitHub Desktop.
Save lakenen/4b37253a21a3d112cef8 to your computer and use it in GitHub Desktop.
IndexedDB data provider for viewer.js
/**
* @fileOverview IndexedDB data provider for viewer.js
* @author lakenen
*/
/*
// Usage example:
var viewer = Crocodoc.createViewer('.viewer', {
url: 'https://viewer.js/assets',
dataProviders: {
metadata: 'indexeddb',
stylesheet: 'indexeddb',
'page-svg': 'indexeddb',
'page-text': 'indexeddb'
}
});
*/
(function () {
'use strict';
var DB_NAME = 'viewer',
DB_VERSION = 1;
var indexedDB = window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
/**
* Wrapper around native indexedDB for a nicer interface
* @param {string} name The DB name
* @param {int} version The DB version to use
* @constructor
*/
function AssetsDB(name, version) {
if (indexedDB) {
this.name = name;
this.version = version;
this.storeName = 'assets';
this.setup(function (err, db) {
if (err) {
this.failed = true;
} else {
this.db = db;
this.ready = true;
}
this.processQueuedActions();
});
this.transactions = {};
this.queue = [];
} else {
// fallback...
this.failed = true;
}
}
AssetsDB.prototype.setup = function (callback) {
var me = this;
var request = indexedDB.open(this.name, this.version);
request.addEventListener('error', function (event) {
console.error('could not open db', event.target.error);
callback.call(me, event.target.error);
});
request.addEventListener('success', function () {
var db = request.result;
db.addEventListener('error', function (event) {
// TODO: figure out what to do with this error...
console.error('db error', event.target.error);
});
callback.call(me, null, db);
});
request.addEventListener('upgradeneeded', function (event) {
var db = event.target.result;
db.createObjectStore(me.storeName, { keyPath: 'url' });
});
};
AssetsDB.prototype.getTransaction = function (mode) {
this.transactions[mode] = this.db.transaction([this.storeName], mode);
return this.transactions[mode];
};
AssetsDB.prototype.getStore = function (mode) {
return this.transactions[mode].objectStore(this.storeName);
};
AssetsDB.prototype._get = function (key) {
try {
return this.getStore('readonly').get(key);
} catch (err) {
this.getTransaction('readonly');
return this._get(key);
}
};
AssetsDB.prototype.get = function (key, callback) {
if (!this.ready) {
this.queue.push(['get', key, callback]);
} else {
if (this.failed) {
callback(new Error('failed to load db'));
return;
}
var req = this._get(key);
req.addEventListener('success', function () {
callback(null, (req.result && req.result.data));
});
req.addEventListener('error', function (event) {
callback(event.target.error);
});
}
};
AssetsDB.prototype._add = function (val) {
try {
return this.getStore('readwrite').add(val);
} catch (err) {
this.getTransaction('readwrite');
return this._add(val);
}
};
AssetsDB.prototype.add = function (key, val, callback) {
if (!this.ready) {
this.queue.push(['add', key, val, callback]);
} else {
if (this.failed) {
callback(new Error('failed to load db'));
return;
}
var req = this._add({
url: key,
data: val
});
req.addEventListener('success', function () {
callback();
});
req.addEventListener('error', function (event) {
callback(event);
});
}
};
AssetsDB.prototype.processQueuedActions = function () {
var i, len, item;
for (i = 0, len = this.queue.length; i < len; ++i) {
item = this.queue[i];
this[item[0]].apply(this, item.slice(1));
}
this.queue = [];
};
Crocodoc.AssetsDB = AssetsDB;
/**
* The indexeddb data provider
* @param {Crocodoc.Scope} scope The scope object associated with this viewer instance
* @returns {Object} The data provider
*/
Crocodoc.addDataProvider('indexeddb', function (scope) {
var db = new Crocodoc.AssetsDB(DB_NAME, DB_VERSION),
promises = {};
return {
get: function (objectType, key) {
var dataProvider = scope.getDataProvider(objectType),
storageKey = dataProvider.getURL(key),
$deferred,
$promise;
// already requested? return the old promise
if (promises[storageKey]) {
return promises[storageKey];
}
$deferred = $.Deferred();
function doNormalRequest(done) {
$promise = dataProvider.get(objectType, key);
$promise.done(function (data) {
// abort the original promise, removing the content
// from memory, because we are duplicating it here
if ($promise) {
$promise.abort();
$promise = null;
}
if (done) {
done(data);
}
$deferred.resolve(data);
});
$promise.fail(function (err) {
$deferred.reject(err);
});
}
promises[storageKey] = $deferred.promise({
abort: function () {
if ($promise) {
$promise.abort();
$promise = null;
}
delete promises[storageKey];
}
});
if (db.failed) {
// fallback to a normal request to the original dp
doNormalRequest();
} else {
// check in the db for this object
db.get(storageKey, function (err, cached) {
if (cached) {
// console.log('cache hit ', cached.url);
$deferred.resolve(cached);
} else {
// console.log('cache miss ', storageKey);
// make a request for the object
doNormalRequest(function (data) {
// save it
db.add(storageKey, data, function (err) {
if (err) {
console.error('error saving', storageKey);
} else {
// console.log('saved', storageKey);
}
});
});
}
});
}
return promises[storageKey];
}
};
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment