Skip to content

Instantly share code, notes, and snippets.

@codyromano
Last active August 11, 2016 14:43
Show Gist options
  • Select an option

  • Save codyromano/3b0106de3b77c1d314c070ba8b7d37e5 to your computer and use it in GitHub Desktop.

Select an option

Save codyromano/3b0106de3b77c1d314c070ba8b7d37e5 to your computer and use it in GitHub Desktop.
/**
* @desc A type-aware wrapper around window.localStorage
* @module Store
*/
(function(exports) {
'use strict';
var proto = Store.prototype;
/* Name of the properties that store type annotations. These
should be unique enough that they don't collide with the
properties of other object literals in localStorage. */
var PROP_TYPE = '_ls_type',
PROP_VALUE = '_ls_value';
function Store(options) {
options = options || {};
// For keeping your data organized
this.namespace = options.namespace || 'main';
if (!window.localStorage) {
throw new Error('localStorage backend is unavailable');
}
}
function getType(value) {
/* undefined needs its own type because it can't
be parsed as an object. If a value is stored as
undefined, we need to explicitly return undefined. */
if (value === undefined) {
return 'undefined';
}
/* typeof null will equal 'object,' but we want to
differentiate between null and object literals */
if (value === null) {
return 'null';
}
if (Array.isArray(value)) {
return 'array';
}
return typeof value;
}
function isTypeEncodedObj(value) {
return (typeof value === 'object' &&
value.hasOwnProperty(PROP_VALUE) &&
value.hasOwnProperty(PROP_TYPE));
}
function typeDecode(value) {
var typeObj, storedValue, storedType, objKey, objVal, decoded;
// Don't decode objects unless they contain type info
if (!isTypeEncodedObj(value)) {
return value;
}
// An object with type annotations
typeObj = value;
storedValue = typeObj[PROP_VALUE];
storedType = typeObj[PROP_TYPE];
switch(storedType) {
case 'undefined':
decoded = undefined;
break;
case 'null':
decoded = null;
break;
case 'number':
decoded = parseFloat(storedValue);
break;
// Recursively decode object properties
case 'array':
decoded = storedValue.map(typeDecode);
break;
case 'object':
for (objKey in storedValue) {
objVal = storedValue[objKey];
typeObj[PROP_VALUE][objKey] = typeDecode(objVal);
}
decoded = typeObj[PROP_VALUE];
break;
default:
// Return numbers, strings and other data types as-is
decoded = typeObj[PROP_VALUE];
break;
}
return decoded;
}
function typeEncode(value) {
var encoded = {}, objKey, objVal;
encoded[PROP_VALUE] = value;
encoded[PROP_TYPE] = getType(value);
switch (encoded[PROP_TYPE]) {
/* If the value is an object (array or object literal),
we need to recursively encode its properties. */
case 'array':
encoded[PROP_VALUE] = encoded[PROP_VALUE].map(typeEncode);
break;
case 'object':
for (objKey in value) {
objVal = value[objKey];
encoded[PROP_VALUE][objKey] = typeEncode(objVal);
}
break;
default:
/* We don't need to do any processing for other data types
such as numbers or strings. */
break;
}
return encoded;
}
/**
* @returns {Object}
*/
function processJSON(string, storeKey, operation) {
var processed = null;
try {
if (operation === 'parse') {
processed = JSON.parse(string);
} else if (operation === 'stringify') {
processed = JSON.stringify(string);
} else {
throw new Error('Unrecognized operation: ' + operation);
}
} catch(err) {
console.error("An exception occurred while processing " +
"locally stored data for '" + storeKey + "': ");
console.log(err);
}
return processed;
}
function getKey(key) {
return [key, this.namespace].join('_');
}
proto.setItem = function(key, value) {
var storeKey = getKey.call(this, key);
var encoded = processJSON(typeEncode(value), storeKey, 'stringify');
localStorage.setItem(storeKey, encoded);
};
proto.getItem = function(key) {
var storeKey = getKey.call(this, key),
encoded = localStorage.getItem(storeKey),
parsed = processJSON(encoded, storeKey, 'parse');
if (!encoded || !parsed) {
return null;
}
return typeDecode(parsed);
};
exports.Store = Store;
})(window);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment