/*! * basket.js * v0.5.1 - 2014-08-16 * http://addyosmani.github.com/basket.js * (c) Addy Osmani; License * Created by: Addy Osmani, Sindre Sorhus, Andrée Hansson, Mat Scales * Contributors: Ironsjp, Mathias Bynens, Rick Waldron, Felipe Morais * Uses rsvp.js, https://github.com/tildeio/rsvp.js */(function( window, document ) { 'use strict'; var head = document.head || document.getElementsByTagName('head')[0]; var storagePrefix = 'basket-'; var defaultExpiration = 5000; var addLocalStorage = function( key, storeObj ) { try { localStorage.setItem( storagePrefix + key, JSON.stringify( storeObj ) ); return true; } catch( e ) { if ( e.name.toUpperCase().indexOf('QUOTA') >= 0 ) { var item; var tempScripts = []; for ( item in localStorage ) { if ( item.indexOf( storagePrefix ) === 0 ) { tempScripts.push( JSON.parse( localStorage[ item ] ) ); } } if ( tempScripts.length ) { tempScripts.sort(function( a, b ) { return a.stamp - b.stamp; }); basket.remove( tempScripts[ 0 ].key ); return addLocalStorage( key, storeObj ); } else { // no files to remove. Larger than available quota return; } } else { // some other error return; } } }; var getUrl = function( url ) { var promise = new RSVP.Promise( function( resolve, reject ){ var xhr = new XMLHttpRequest(); xhr.open( 'GET', url ); xhr.onreadystatechange = function() { if ( xhr.readyState === 4 ) { if( xhr.status === 200 ) { resolve( { content: xhr.responseText, type: xhr.getResponseHeader('content-type') } ); } else { reject( new Error( xhr.statusText ) ); } } }; // By default XHRs never timeout, and even Chrome doesn't implement the // spec for xhr.timeout. So we do it ourselves. setTimeout( function () { if( xhr.readyState < 4 ) { xhr.abort(); } }, basket.timeout ); xhr.send(); }); return promise; }; var saveUrl = function( obj ) { return getUrl( obj.url ).then( function( result ) { var storeObj = wrapStoreData( obj, result ); if (!obj.skipCache) { addLocalStorage( obj.key , storeObj ); } return storeObj; }); }; var wrapStoreData = function( obj, data ) { var now = +new Date(); obj.data = data.content; obj.originalType = data.type; obj.type = obj.type || data.type; obj.skipCache = obj.skipCache || false; obj.stamp = now; obj.expire = now + ( ( obj.expire || defaultExpiration ) * 60 * 60 * 1000 ); return obj; }; var isCacheValid = function(source, obj) { return !source || source.expire - +new Date() < 0 || obj.unique !== source.unique || (basket.isValidItem && !basket.isValidItem(source, obj)); }; var handleStackObject = function( obj ) { var source, promise, shouldFetch; if ( !obj.url ) { return; } obj.key = ( obj.key || obj.url ); source = basket.get( obj.key ); obj.execute = obj.execute !== false; shouldFetch = isCacheValid(source, obj); if( obj.live || shouldFetch ) { if ( obj.unique ) { // set parameter to prevent browser cache obj.url += ( ( obj.url.indexOf('?') > 0 ) ? '&' : '?' ) + 'basket-unique=' + obj.unique; } promise = saveUrl( obj ); if( obj.live && !shouldFetch ) { promise = promise .then( function( result ) { // If we succeed, just return the value // RSVP doesn't have a .fail convenience method return result; }, function() { return source; }); } } else { source.type = obj.type || source.originalType; promise = new RSVP.Promise( function( resolve ){ resolve( source ); }); } return promise; }; var injectScript = function( obj ) { var script = document.createElement('script'); script.defer = true; // Have to use .text, since we support IE8, // which won't allow appending to a script script.text = obj.data; head.appendChild( script ); }; var handlers = { 'default': injectScript }; var execute = function( obj ) { if( obj.type && handlers[ obj.type ] ) { return handlers[ obj.type ]( obj ); } return handlers['default']( obj ); // 'default' is a reserved word }; var performActions = function( resources ) { resources.map( function( obj ) { if( obj.execute ) { execute( obj ); } return obj; } ); }; var fetch = function() { var i, l, promises = []; for ( i = 0, l = arguments.length; i < l; i++ ) { promises.push( handleStackObject( arguments[ i ] ) ); } return RSVP.all( promises ); }; var thenRequire = function() { var resources = fetch.apply( null, arguments ); var promise = this.then( function() { return resources; }).then( performActions ); promise.thenRequire = thenRequire; return promise; }; window.basket = { require: function() { var promise = fetch.apply( null, arguments ).then( performActions ); promise.thenRequire = thenRequire; return promise; }, remove: function( key ) { localStorage.removeItem( storagePrefix + key ); return this; }, get: function( key ) { var item = localStorage.getItem( storagePrefix + key ); try { return JSON.parse( item || 'false' ); } catch( e ) { return false; } }, clear: function( expired ) { var item, key; var now = +new Date(); for ( item in localStorage ) { key = item.split( storagePrefix )[ 1 ]; if ( key && ( !expired || this.get( key ).expire <= now ) ) { this.remove( key ); } } return this; }, isValidItem: null, timeout: 5000, addHandler: function( types, handler ) { if( !Array.isArray( types ) ) { types = [ types ]; } types.forEach( function( type ) { handlers[ type ] = handler; }); }, removeHandler: function( types ) { basket.addHandler( types, undefined ); } }; // delete expired keys basket.clear( true ); })( this, document );