Skip to content

Instantly share code, notes, and snippets.

@JawsomeJason
Created September 21, 2013 00:00
Show Gist options
  • Save JawsomeJason/6645493 to your computer and use it in GitHub Desktop.
Save JawsomeJason/6645493 to your computer and use it in GitHub Desktop.
Stores inlined JS/CSS into localStorage, and then helps you re-inject it on page reload
/**
* @fileOverview Stores inline JS/CSS into localStorage
* @author "Jason T. Featheringham" <jason@thejase.com>
* @requires this.localStorage,
* this.document,
* this.JSON,
* this.document.querySelectorAll,
*
* How It Works:
* On your server, when you want some content stored locally instead of retrieved from the server. An example using a script file (script.js), but any DOM element will work.
*
* 1. Check cookies[:codecache].
* * If the cookie doesn't exist, inline the content:
* <script data-codecache="script.js"> alert('hello world'); </script>
* * If the cookie === 'false', the browser does not support the right features. Use an old-school script[src]:
* <script src="script.js"></script>
* * Otherwise, the cookie will be an array of stored [data-codecache] ids. If this script's ID exists, simply create a placeholder:
* <script data-codecache="script.js"></script>
* 2. It's smarter if your inlined content is in the <head>
* 3. Inline this code afterwards
* 4. call `new CodeCache();`
*/
// the server should not include this code if cookies[:codecache] === 'false'
( function( window, undefined ) {
// Feature detection
var storage = window.localStorage && window.localStorage.getItem && window.localStorage.setItem && window.localStorage,
JSON = window.JSON && window.JSON.parse && window.JSON.stringify && window.JSON,
document = window.document,
query = document && document.querySelectorAll,
supported = storage && query && JSON;
if( !supported ) {
// browser can't support codecache; let the server know so they can use script[src];
document.cookie = 'codecache=false';
}
/**
* Stores element data in localStorage and injects into page when requested.
* @returns {CodeCache}
* @constructor
* @description
* Value of attribute is identifier
* If element is empty, populate with stored data.
* Otherwise, store current element's data
*/
var CodeCache = function() {
var codecache = 'codecache',
// find existing cookie values representing keys stored items, if anything
match = document.cookie.match( new RegExp( '(^|;) ?' + codecache + '=([^;]*)(;|$)' ) ),
// find existing localstorage items, if anything
store = JSON.parse( storage.getItem( codecache ) ) || {};
this._store = store;
return this.scan();
};
/**
* Gets keys from the store
* @returns {Array}
*/
CodeCache.prototype.keys = function() {
var keys = [],
key;
if( 'keys' in Object ) {
return Object.keys( this._store );
}
else {
for( key in this._store ) {
this._store.hasOwnProperty( key ) && keys.push( key );
}
return keys;
}
};
/**
* Gets and sets cached code
* @param key
* @param value
* @returns {Object|String|CodeCache}
*/
CodeCache.prototype.store = function( key, value ) {
var _this = this,
update = function() {
window.localStorage.setItem( 'codecache', JSON.stringify( _this._store ) );
document.cookie = 'codecache=' + JSON.stringify( _this.keys() );
};
// get all keys
if( key === undefined ) {
return this._store;
}
// get specific key value
else if( value === undefined ) {
return this._store.hasOwnProperty( key ) && this._store[ key ] || undefined;
}
// delete a key
else if( value === null && this._store.hasOwnProperty( key ) ) {
delete this._store[ key ];
update();
return this;
}
// set a key
else {
this._store[ key ] = value;
update();
return this;
}
};
/** determines property name for the inner content of a node */
CodeCache.prototype._textProp = document && document.head && 'textContent' in document.head && 'textContent' || 'innerText';
CodeCache.prototype.scan = function() {
// get all items that are set to be stored
var scripts = document.querySelectorAll( '[data-codecache]'),
i, max, script,
isEmpty = function( text ) { return text.replace(/^\s\s*/, '').replace(/\s\s*$/, '') === ''; };
for( i = 0, max = scripts.length; i < max; i++ ) {
script = scripts[i];
if( isEmpty( script[this._textProp] ) ) {
// needs us to fill it in
this.inject( script );
}
else {
// we need to store it in localStorage
this.add( script );
}
}
return this;
};
/**
* Adds a DOM element to cache
* @param {Node} node
* @returns {CodeCache}
*/
CodeCache.prototype.add = function( node ) {
var cachekey = node.attributes.getNamedItem('data-codecache').value,
content = node[ this._textProp ];
this.store( cachekey, content );
return this;
};
/**
* Inject a cached element into the DOM
* @param {Node} node
* @returns {CodeCache}
*/
CodeCache.prototype.inject = function( node ) {
var parent = node.parentNode,
newNode = node.cloneNode( true ),
cachekey = node.attributes.getNamedItem('data-codecache').value;
newNode[ this._textProp ] = this.store( cachekey );
parent.insertBefore( newNode, node );
parent.removeChild( node );
return this;
}
// expose to global
window.CodeCache = CodeCache;
} )( this );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment