Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a tiny function to tack tiny UUID's to end of LocalStorage keys instead of a seperate indexer #185

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 172 additions & 80 deletions src/adapters/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,97 @@
//
Lawnchair.adapter('dom', (function() {
var storage = window.localStorage
// the indexer is an encapsulation of the helpers needed to keep an ordered index of the keys
var indexer = function(name) {
return {
// the key
key: name + '._index_',
// returns the index
all: function() {
var a = storage.getItem(JSON.stringify(this.key))
if (a) {
a = JSON.parse(a)
}
if (a === null) storage.setItem(JSON.stringify(this.key), JSON.stringify([])) // lazy init
return JSON.parse(storage.getItem(JSON.stringify(this.key)))
},
// adds a key to the index
add: function (key) {
var a = this.all()
a.push(key)
storage.setItem(JSON.stringify(this.key), JSON.stringify(a))
},
// deletes a key from the index
del: function (key) {
var a = this.all(), r = []
// FIXME this is crazy inefficient but I'm in a strata meeting and half concentrating
for (var i = 0, l = a.length; i < l; i++) {
if (a[i] != key) r.push(a[i])
}
storage.setItem(JSON.stringify(this.key), JSON.stringify(r))
},
// returns index for a key
find: function (key) {
var a = this.all()
for (var i = 0, l = a.length; i < l; i++) {
if (key === a[i]) return i

// Generates (quite) small incremental UUID's which are tacked onto the end
// of LocalStorage keys to remove the requirement for a seperate indexer.
//
// NOTE: Taken from https://github.com/forbesmyester/SyncIt, Could be used
// for Lawnchair.uuid()
var getTLIdEncoderDecoder = function(epoch,uniqueLength) {

var lastDate = null;
var index = -1;
if (uniqueLength === undefined) {
uniqueLength = 1;
}

if (typeof epoch != 'number') {
throw "Only takes timestamps";
}

var genUid = function(now) {

if (now === undefined) {
now = new Date().getTime();
}
if (typeof now == 'object') {
throw "Only takes timestamps";
}

if ((lastDate === null) || (now !== lastDate)) {
index = -1;
}

var superUnique = (++index).toString(32);
if (superUnique.length < uniqueLength) {
superUnique = '0' + superUnique;
}
var timeEncoded = (now - epoch).toString(32);

if (superUnique.length > uniqueLength) {
throw "getUidGenerator.genUid cannot generate TLId until next millisecond!";
}

lastDate = now;
if (timeEncoded.substr(0,1) <= '9') {
return "X"+timeEncoded+superUnique;
}
return timeEncoded+superUnique;
};

var uidToTimestamp = function(tlid) {
if (tlid.substr(0,1) == 'X') {
tlid = tlid.substr(1);
}
tlid = tlid.substr(0, tlid.length - uniqueLength);
return parseInt(tlid,32) + epoch;
};

var sort = function(tlidA, tlidB) {
if (tlidA.substr(0,1) == 'X') {
tlidA = tlidA.substr(1);
}
if (tlidB.substr(0,1) == 'X') {
tlidB = tlidB.substr(1);
}
tlidA = tlidA.replace(/.*\./,'');
tlidB = tlidB.replace(/.*\./,'');
if (tlidA.length != tlidB.length) {
return (tlidA.length < tlidB.length) ? -1 : 1;
}
return (tlidA < tlidB) ? -1 : 1;
};

return {encode: genUid, decode: uidToTimestamp, sort: sort};
};

var keyCollector = function(inst, searchKey) {
var keys = [];
var key;
var subKey;
for (var i = 0, l = storage.length; i<l; i++) {
key = storage.key(i);
if (key.substring(0, inst.name.length + 1) == inst.name + ".") {
if (
(searchKey === undefined) ||
(key.substring(inst.name.length + 1).replace(/\.[^\.]+$/,'') == searchKey)
) {
keys.push(key.substring(inst.name.length + 1));
}
return false
}
}
keys.sort(inst.indexer.sort);
return keys;
}

// adapter api
Expand All @@ -72,18 +125,29 @@ Lawnchair.adapter('dom', (function() {
},

init: function (options, callback) {
this.indexer = indexer(this.name)
this.indexer = getTLIdEncoderDecoder(new Date(2013,9,19).getTime());
if (callback) this.fn(this.name, callback).call(this, this)
},

save: function (obj, callback) {
var key = obj.key ? this.name + '.' + obj.key : this.name + '.' + this.uuid()
// now we kil the key and use it in the store colleciton
var key;
// Calculate key in localStorage
if (obj.key) {
key = keyCollector(this, obj.key);
if (key.length) {
key = this.name + '.' + key[0];
} else {
key = this.name + '.' + obj.key + '.' + this.indexer.encode();
}
} else {
key = this.name + '.' + this.uuid() +'.' + this.indexer.encode();
}

// now we kil the key and use it in the store colleciton
delete obj.key;
storage.setItem(key, JSON.stringify(obj))
// if the key is not in the index push it on
if (this.indexer.find(key) === false) this.indexer.add(key)
obj.key = key.slice(this.name.length + 1)
obj.key = key.slice(this.name.length + 1).replace(/\.[^.]+$/,'');
if (callback) {
this.lambda(callback).call(this, obj)
}
Expand All @@ -101,69 +165,96 @@ Lawnchair.adapter('dom', (function() {
if (callback) this.lambda(callback).call(this, saved)
return this
},

// accepts [options], callback
keys: function(callback) {

var replaceFunc = function(r){
return r.replace(/\.[^\.]+$/,'')
};

var keys = [];

if (callback) {
var name = this.name
var indices = this.indexer.all();
var keys = [];
//Checking for the support of map.
if(Array.prototype.map) {
keys = indices.map(function(r){ return r.replace(name + '.', '') })
keys = keyCollector(this).map(replaceFunc)
} else {
for (var key in indices) {
keys.push(key.replace(name + '.', ''));
var indices = keyCollector(this);
for (var i = 0, l = indices.length; i < l; i++) {
keys.push(replaceFunc(indices[i]));
}
}

this.fn('keys', callback).call(this, keys)
}
return this // TODO options for limit/offset, return promise
},

get: function (key, callback) {

var getRealKeyObj = function(realKey) {
var r = {};
for (var i = 0, l = realKey.length; i < l; i++) {
r[realKey[i].replace(/\.[^\.]+$/,'')] = realKey[i].replace(/.*\./,'');
}
return r;
}

var obj = null,
realKeyObj,
realKey;

if (this.isArray(key)) {
realKeyObj = getRealKeyObj(keyCollector(this));

var r = []
for (var i = 0, l = key.length; i < l; i++) {
var k = this.name + '.' + key[i]
var obj = storage.getItem(k)
if (obj) {
obj = JSON.parse(obj)
obj.key = key[i]
}
obj = null;
if (realKeyObj.hasOwnProperty(key[i])) {
obj = storage.getItem(this.name + '.' + key[i] + '.' + realKeyObj[key[i]]);
if (obj) {
obj = JSON.parse(obj)
obj.key = key[i]
}
}
r.push(obj)
}
if (callback) this.lambda(callback).call(this, r)
} else {
var k = this.name + '.' + key
var obj = storage.getItem(k)
if (obj) {
obj = JSON.parse(obj)
obj.key = key
}
realKey = keyCollector(this, key);
if (realKey.length) {
obj = storage.getItem(this.name + '.' + realKey[0]);
if (obj) {
obj = JSON.parse(obj)
obj.key = key
}
}
if (callback) this.lambda(callback).call(this, obj)
}
return this
},

exists: function (key, cb) {
var exists = this.indexer.find(this.name+'.'+key) === false ? false : true ;
var exists = keyCollector(this, key).length ? true : false;
this.lambda(cb).call(this, exists);
return this;
},
// NOTE adapters cannot set this.__results but plugins do
// this probably should be reviewed
all: function (callback) {
var idx = this.indexer.all()
, r = []

var realKey = keyCollector(this)
, r = []
, o
, k
for (var i = 0, l = idx.length; i < l; i++) {
k = idx[i] //v
o = JSON.parse(storage.getItem(k))
o.key = k.replace(this.name + '.', '')
r.push(o)
for (var i = 0, l = realKey.length; i < l; i++) {
o = storage.getItem(this.name + '.' + realKey[i]);
if (o) {
o = JSON.parse(o)
o.key = realKey[i].replace(/\.[^\.]+$/,'');
}
r.push(o);
}

if (callback) this.fn(this.name, callback).call(this, r)
return this
},
Expand All @@ -185,21 +276,22 @@ Lawnchair.adapter('dom', (function() {
removeOne(i);
return this;
}
var key = this.name + '.' +
((keyOrArray.key) ? keyOrArray.key : keyOrArray)
this.indexer.del(key)
storage.removeItem(key)

var realKey = keyCollector(this, keyOrArray.key ? keyOrArray.key : keyOrArray);
if (realKey.length) {
storage.removeItem(this.name + '.' + realKey[0])
}

if (callback) this.lambda(callback).call(this)
return this
},

nuke: function (callback) {
this.all(function(r) {
for (var i = 0, l = r.length; i < l; i++) {
this.remove(r[i]);
}
if (callback) this.lambda(callback).call(this)
})
return this
var realKey = keyCollector(this);
for (var i = 0, l = realKey.length; i < l; i++) {
storage.removeItem(this.name + '.' + realKey[i])
}
if (callback) this.lambda(callback).call(this)
return this
}
}})());