1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/8ab88925e5ef/ Changeset: 8ab88925e5ef User: carlfeberhard Date: 2013-08-21 17:47:11 Summary: JS libraries: update jStorage to 4.4 Affected #: 3 files diff -r c528daf3e3b3936dc399d6da4cfe4e636f4a556c -r 8ab88925e5eff54a056722bdf0fb3622d0a0455c static/scripts/libs/jquery/jstorage.js --- a/static/scripts/libs/jquery/jstorage.js +++ b/static/scripts/libs/jquery/jstorage.js @@ -3,7 +3,7 @@ * Simple local storage wrapper to save data on the browser side, supporting * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+ * - * Copyright (c) 2010 Andris Reinman, andris.reinman@gmail.com + * Copyright (c) 2010 - 2012 Andris Reinman, andris.reinman@gmail.com * Project homepage: www.jstorage.info * * Licensed under MIT-style license: @@ -24,78 +24,66 @@ * SOFTWARE. */ -/** - * $.jStorage - * - * USAGE: - * - * jStorage requires Prototype, MooTools or jQuery! If jQuery is used, then - * jQuery-JSON (http://code.google.com/p/jquery-json/) is also needed. - * (jQuery-JSON needs to be loaded BEFORE jStorage!) - * - * Methods: - * - * -set(key, value) - * $.jStorage.set(key, value) -> saves a value - * - * -get(key[, default]) - * value = $.jStorage.get(key [, default]) -> - * retrieves value if key exists, or default if it doesn't - * - * -deleteKey(key) - * $.jStorage.deleteKey(key) -> removes a key from the storage - * - * -flush() - * $.jStorage.flush() -> clears the cache - * - * -storageObj() - * $.jStorage.storageObj() -> returns a read-ony copy of the actual storage - * - * -storageSize() - * $.jStorage.storageSize() -> returns the size of the storage in bytes - * - * -index() - * $.jStorage.index() -> returns the used keys as an array - * - * -storageAvailable() - * $.jStorage.storageAvailable() -> returns true if storage is available - * - * -reInit() - * $.jStorage.reInit() -> reloads the data from browser storage - * - * <value> can be any JSON-able value, including objects and arrays. - * - **/ + (function(){ + var + /* jStorage version */ + JSTORAGE_VERSION = "0.4.4", -(function($){ - if(!$ || !($.toJSON || Object.toJSON || window.JSON)){ - throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!"); + /* detect a dollar object or create one if not found */ + $ = window.jQuery || window.$ || (window.$ = {}), + + /* check for a JSON handling support */ + JSON = { + parse: + window.JSON && (window.JSON.parse || window.JSON.decode) || + String.prototype.evalJSON && function(str){return String(str).evalJSON();} || + $.parseJSON || + $.evalJSON, + stringify: + Object.toJSON || + window.JSON && (window.JSON.stringify || window.JSON.encode) || + $.toJSON + }; + + // Break if no JSON support was found + if(!('parse' in JSON) || !('stringify' in JSON)){ + throw new Error("No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page"); } - + var - /* This is the object, that holds the cached values */ - _storage = {}, + /* This is the object, that holds the cached values */ + _storage = {__jstorage_meta:{CRC32:{}}}, /* Actual browser storage (localStorage or globalStorage['domain']) */ _storage_service = {jStorage:"{}"}, /* DOM element for older IE versions, holds userData behavior */ _storage_elm = null, - + /* How much space does the storage take */ _storage_size = 0, - /* function to encode objects to JSON strings */ - json_encode = $.toJSON || Object.toJSON || (window.JSON && (JSON.encode || JSON.stringify)), + /* which backend is currently used */ + _backend = false, - /* function to decode objects from JSON strings */ - json_decode = $.evalJSON || (window.JSON && (JSON.decode || JSON.parse)) || function(str){ - return String(str).evalJSON(); - }, - - /* which backend is currently used */ - _backend = false; - + /* onchange observers */ + _observers = {}, + + /* timeout to wait after onchange event */ + _observer_timeout = false, + + /* last update time */ + _observer_update = 0, + + /* pubsub observers */ + _pubsub_observers = {}, + + /* skip published items older than current timestamp */ + _pubsub_last = +new Date(), + + /* Next check for TTL */ + _ttl_timeout, + /** * XML encoding and decoding as XML nodes can't be JSON'ized * XML nodes are encoded and decoded if the node is the value to be saved @@ -105,7 +93,7 @@ * $.jStorage.set("key", {xml: xmlNode}); // NOT OK */ _XMLService = { - + /** * Validates a XML node to be XML * based on jQuery.isXML function @@ -114,7 +102,7 @@ var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }, - + /** * Encodes a XML node to string * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/ @@ -132,7 +120,7 @@ } return false; }, - + /** * Decodes a XML node from string * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/ @@ -154,20 +142,33 @@ } }; + ////////////////////////// PRIVATE METHODS //////////////////////// /** * Initialization function. Detects if the browser supports DOM Storage * or userData behavior and behaves accordingly. - * @returns undefined */ function _init(){ /* Check if browser supports localStorage */ + var localStorageReallyWorks = false; if("localStorage" in window){ try { + window.localStorage.setItem('_tmptest', 'tmpval'); + localStorageReallyWorks = true; + window.localStorage.removeItem('_tmptest'); + } catch(BogusQuotaExceededErrorOnIos5) { + // Thanks be to iOS5 Private Browsing mode which throws + // QUOTA_EXCEEDED_ERRROR DOM Exception 22. + } + } + + if(localStorageReallyWorks){ + try { if(window.localStorage) { _storage_service = window.localStorage; _backend = "localStorage"; + _observer_update = _storage_service.jStorage_update; } } catch(E3) {/* Firefox fails when touching localStorage and cookies are disabled */} } @@ -175,8 +176,14 @@ else if("globalStorage" in window){ try { if(window.globalStorage) { - _storage_service = window.globalStorage[window.location.hostname]; + if(window.location.hostname == 'localhost'){ + _storage_service = window.globalStorage['localhost.localdomain']; + } + else{ + _storage_service = window.globalStorage[window.location.hostname]; + } _backend = "globalStorage"; + _observer_update = _storage_service.jStorage_update; } } catch(E4) {/* Firefox fails when touching localStorage and cookies are disabled */} } @@ -191,11 +198,24 @@ /* userData element needs to be inserted into the DOM! */ document.getElementsByTagName('head')[0].appendChild(_storage_elm); - _storage_elm.load("jStorage"); + try{ + _storage_elm.load("jStorage"); + }catch(E){ + // try to reset cache + _storage_elm.setAttribute("jStorage", "{}"); + _storage_elm.save("jStorage"); + _storage_elm.load("jStorage"); + } + var data = "{}"; try{ data = _storage_elm.getAttribute("jStorage"); }catch(E5){} + + try{ + _observer_update = _storage_elm.getAttribute("jStorage_update"); + }catch(E6){} + _storage_service.jStorage = data; _backend = "userDataBehavior"; }else{ @@ -204,32 +224,217 @@ } } + // Load data from storage _load_storage(); + + // remove dead keys + _handleTTL(); + + // start listening for changes + _setupObserver(); + + // initialize publish-subscribe service + _handlePubSub(); + + // handle cached navigation + if("addEventListener" in window){ + window.addEventListener("pageshow", function(event){ + if(event.persisted){ + _storageObserver(); + } + }, false); + } } - + + /** + * Reload data from storage when needed + */ + function _reloadData(){ + var data = "{}"; + + if(_backend == "userDataBehavior"){ + _storage_elm.load("jStorage"); + + try{ + data = _storage_elm.getAttribute("jStorage"); + }catch(E5){} + + try{ + _observer_update = _storage_elm.getAttribute("jStorage_update"); + }catch(E6){} + + _storage_service.jStorage = data; + } + + _load_storage(); + + // remove dead keys + _handleTTL(); + + _handlePubSub(); + } + + /** + * Sets up a storage change observer + */ + function _setupObserver(){ + if(_backend == "localStorage" || _backend == "globalStorage"){ + if("addEventListener" in window){ + window.addEventListener("storage", _storageObserver, false); + }else{ + document.attachEvent("onstorage", _storageObserver); + } + }else if(_backend == "userDataBehavior"){ + setInterval(_storageObserver, 1000); + } + } + + /** + * Fired on any kind of data change, needs to check if anything has + * really been changed + */ + function _storageObserver(){ + var updateTime; + // cumulate change notifications with timeout + clearTimeout(_observer_timeout); + _observer_timeout = setTimeout(function(){ + + if(_backend == "localStorage" || _backend == "globalStorage"){ + updateTime = _storage_service.jStorage_update; + }else if(_backend == "userDataBehavior"){ + _storage_elm.load("jStorage"); + try{ + updateTime = _storage_elm.getAttribute("jStorage_update"); + }catch(E5){} + } + + if(updateTime && updateTime != _observer_update){ + _observer_update = updateTime; + _checkUpdatedKeys(); + } + + }, 25); + } + + /** + * Reloads the data and checks if any keys are changed + */ + function _checkUpdatedKeys(){ + var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)), + newCrc32List; + + _reloadData(); + newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)); + + var key, + updated = [], + removed = []; + + for(key in oldCrc32List){ + if(oldCrc32List.hasOwnProperty(key)){ + if(!newCrc32List[key]){ + removed.push(key); + continue; + } + if(oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0,2) == "2."){ + updated.push(key); + } + } + } + + for(key in newCrc32List){ + if(newCrc32List.hasOwnProperty(key)){ + if(!oldCrc32List[key]){ + updated.push(key); + } + } + } + + _fireObservers(updated, "updated"); + _fireObservers(removed, "deleted"); + } + + /** + * Fires observers for updated keys + * + * @param {Array|String} keys Array of key names or a key + * @param {String} action What happened with the value (updated, deleted, flushed) + */ + function _fireObservers(keys, action){ + keys = [].concat(keys || []); + if(action == "flushed"){ + keys = []; + for(var key in _observers){ + if(_observers.hasOwnProperty(key)){ + keys.push(key); + } + } + action = "deleted"; + } + for(var i=0, len = keys.length; i<len; i++){ + if(_observers[keys[i]]){ + for(var j=0, jlen = _observers[keys[i]].length; j<jlen; j++){ + _observers[keys[i]][j](keys[i], action); + } + } + if(_observers["*"]){ + for(var j=0, jlen = _observers["*"].length; j<jlen; j++){ + _observers["*"][j](keys[i], action); + } + } + } + } + + /** + * Publishes key change to listeners + */ + function _publishChange(){ + var updateTime = (+new Date()).toString(); + + if(_backend == "localStorage" || _backend == "globalStorage"){ + try { + _storage_service.jStorage_update = updateTime; + } catch (E8) { + // safari private mode has been enabled after the jStorage initialization + _backend = false; + } + }else if(_backend == "userDataBehavior"){ + _storage_elm.setAttribute("jStorage_update", updateTime); + _storage_elm.save("jStorage"); + } + + _storageObserver(); + } + /** * Loads the data from the storage based on the supported mechanism - * @returns undefined */ function _load_storage(){ /* if jStorage string is retrieved, then decode it */ if(_storage_service.jStorage){ try{ - _storage = json_decode(String(_storage_service.jStorage)); + _storage = JSON.parse(String(_storage_service.jStorage)); }catch(E6){_storage_service.jStorage = "{}";} }else{ _storage_service.jStorage = "{}"; } - _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; + _storage_size = _storage_service.jStorage?String(_storage_service.jStorage).length:0; + + if(!_storage.__jstorage_meta){ + _storage.__jstorage_meta = {}; + } + if(!_storage.__jstorage_meta.CRC32){ + _storage.__jstorage_meta.CRC32 = {}; + } } /** * This functions provides the "save" mechanism to store the jStorage object - * @returns undefined */ function _save(){ + _dropOldEvents(); // remove expired events try{ - _storage_service.jStorage = json_encode(_storage); + _storage_service.jStorage = JSON.stringify(_storage); // If userData is used as the storage engine, additional if(_storage_elm) { _storage_elm.setAttribute("jStorage",_storage_service.jStorage); @@ -241,52 +446,256 @@ /** * Function checks if a key is set and is string or numberic + * + * @param {String} key Key name */ function _checkKey(key){ if(!key || (typeof key != "string" && typeof key != "number")){ throw new TypeError('Key name must be string or numeric'); } + if(key == "__jstorage_meta"){ + throw new TypeError('Reserved key name'); + } return true; } + /** + * Removes expired keys + */ + function _handleTTL(){ + var curtime, i, TTL, CRC32, nextExpire = Infinity, changed = false, deleted = []; + + clearTimeout(_ttl_timeout); + + if(!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != "object"){ + // nothing to do here + return; + } + + curtime = +new Date(); + TTL = _storage.__jstorage_meta.TTL; + + CRC32 = _storage.__jstorage_meta.CRC32; + for(i in TTL){ + if(TTL.hasOwnProperty(i)){ + if(TTL[i] <= curtime){ + delete TTL[i]; + delete CRC32[i]; + delete _storage[i]; + changed = true; + deleted.push(i); + }else if(TTL[i] < nextExpire){ + nextExpire = TTL[i]; + } + } + } + + // set next check + if(nextExpire != Infinity){ + _ttl_timeout = setTimeout(_handleTTL, nextExpire - curtime); + } + + // save changes + if(changed){ + _save(); + _publishChange(); + _fireObservers(deleted, "deleted"); + } + } + + /** + * Checks if there's any events on hold to be fired to listeners + */ + function _handlePubSub(){ + var i, len; + if(!_storage.__jstorage_meta.PubSub){ + return; + } + var pubelm, + _pubsubCurrent = _pubsub_last; + + for(i=len=_storage.__jstorage_meta.PubSub.length-1; i>=0; i--){ + pubelm = _storage.__jstorage_meta.PubSub[i]; + if(pubelm[0] > _pubsub_last){ + _pubsubCurrent = pubelm[0]; + _fireSubscribers(pubelm[1], pubelm[2]); + } + } + + _pubsub_last = _pubsubCurrent; + } + + /** + * Fires all subscriber listeners for a pubsub channel + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload data to deliver + */ + function _fireSubscribers(channel, payload){ + if(_pubsub_observers[channel]){ + for(var i=0, len = _pubsub_observers[channel].length; i<len; i++){ + // send immutable data that can't be modified by listeners + _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload))); + } + } + } + + /** + * Remove old events from the publish stream (at least 2sec old) + */ + function _dropOldEvents(){ + if(!_storage.__jstorage_meta.PubSub){ + return; + } + + var retire = +new Date() - 2000; + + for(var i=0, len = _storage.__jstorage_meta.PubSub.length; i<len; i++){ + if(_storage.__jstorage_meta.PubSub[i][0] <= retire){ + // deleteCount is needed for IE6 + _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i); + break; + } + } + + if(!_storage.__jstorage_meta.PubSub.length){ + delete _storage.__jstorage_meta.PubSub; + } + + } + + /** + * Publish payload to a channel + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload to send to the subscribers + */ + function _publish(channel, payload){ + if(!_storage.__jstorage_meta){ + _storage.__jstorage_meta = {}; + } + if(!_storage.__jstorage_meta.PubSub){ + _storage.__jstorage_meta.PubSub = []; + } + + _storage.__jstorage_meta.PubSub.unshift([+new Date, channel, payload]); + + _save(); + _publishChange(); + } + + + /** + * JS Implementation of MurmurHash2 + * + * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed) + * + * @author <a href="mailto:gary.court@gmail.com">Gary Court</a> + * @see http://github.com/garycourt/murmurhash-js + * @author <a href="mailto:aappleby@gmail.com">Austin Appleby</a> + * @see http://sites.google.com/site/murmurhash/ + * + * @param {string} str ASCII only + * @param {number} seed Positive integer only + * @return {number} 32-bit positive integer hash + */ + + function murmurhash2_32_gc(str, seed) { + var + l = str.length, + h = seed ^ l, + i = 0, + k; + + while (l >= 4) { + k = + ((str.charCodeAt(i) & 0xff)) | + ((str.charCodeAt(++i) & 0xff) << 8) | + ((str.charCodeAt(++i) & 0xff) << 16) | + ((str.charCodeAt(++i) & 0xff) << 24); + + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + k ^= k >>> 24; + k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k; + + l -= 4; + ++i; + } + + switch (l) { + case 3: h ^= (str.charCodeAt(i + 2) & 0xff) << 16; + case 2: h ^= (str.charCodeAt(i + 1) & 0xff) << 8; + case 1: h ^= (str.charCodeAt(i) & 0xff); + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + } + + h ^= h >>> 13; + h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)); + h ^= h >>> 15; + + return h >>> 0; + } + ////////////////////////// PUBLIC INTERFACE ///////////////////////// $.jStorage = { /* Version number */ - version: "0.1.5.0", + version: JSTORAGE_VERSION, /** * Sets a key's value. - * - * @param {String} key - Key to set. If this value is not set or not + * + * @param {String} key Key to set. If this value is not set or not * a string an exception is raised. - * @param value - Value to set. This can be any value that is JSON + * @param {Mixed} value Value to set. This can be any value that is JSON * compatible (Numbers, Strings, Objects etc.). - * @returns the used value + * @param {Object} [options] - possible options to use + * @param {Number} [options.TTL] - optional TTL value + * @return {Mixed} the used value */ - set: function(key, value){ + set: function(key, value, options){ _checkKey(key); + + options = options || {}; + + // undefined values are deleted automatically + if(typeof value == "undefined"){ + this.deleteKey(key); + return value; + } + if(_XMLService.isXML(value)){ value = {_is_xml:true,xml:_XMLService.encode(value)}; + }else if(typeof value == "function"){ + return undefined; // functions can't be saved! + }else if(value && typeof value == "object"){ + // clone the object before saving to _storage tree + value = JSON.parse(JSON.stringify(value)); } + _storage[key] = value; - _save(); + + _storage.__jstorage_meta.CRC32[key] = "2." + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c); + + this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange + + _fireObservers(key, "updated"); return value; }, - + /** * Looks up a key in cache - * + * * @param {String} key - Key to look up. * @param {mixed} def - Default value to return, if key didn't exist. - * @returns the key value, default value or <null> + * @return {Mixed} the key value, default value or null */ get: function(key, def){ _checkKey(key); if(key in _storage){ - if(typeof _storage[key] == "object" && - _storage[key]._is_xml && - _storage[key]._is_xml){ + if(_storage[key] && typeof _storage[key] == "object" && _storage[key]._is_xml) { return _XMLService.decode(_storage[key].xml); }else{ return _storage[key]; @@ -294,127 +703,231 @@ } return typeof(def) == 'undefined' ? null : def; }, - + /** * Deletes a key from cache. - * + * * @param {String} key - Key to delete. - * @returns true if key existed or false if it didn't + * @return {Boolean} true if key existed or false if it didn't */ deleteKey: function(key){ _checkKey(key); if(key in _storage){ delete _storage[key]; + // remove from TTL list + if(typeof _storage.__jstorage_meta.TTL == "object" && + key in _storage.__jstorage_meta.TTL){ + delete _storage.__jstorage_meta.TTL[key]; + } + + delete _storage.__jstorage_meta.CRC32[key]; + _save(); + _publishChange(); + _fireObservers(key, "deleted"); return true; } return false; }, /** + * Sets a TTL for a key, or remove it if ttl value is 0 or below + * + * @param {String} key - key to set the TTL for + * @param {Number} ttl - TTL timeout in milliseconds + * @return {Boolean} true if key existed or false if it didn't + */ + setTTL: function(key, ttl){ + var curtime = +new Date(); + _checkKey(key); + ttl = Number(ttl) || 0; + if(key in _storage){ + + if(!_storage.__jstorage_meta.TTL){ + _storage.__jstorage_meta.TTL = {}; + } + + // Set TTL value for the key + if(ttl>0){ + _storage.__jstorage_meta.TTL[key] = curtime + ttl; + }else{ + delete _storage.__jstorage_meta.TTL[key]; + } + + _save(); + + _handleTTL(); + + _publishChange(); + return true; + } + return false; + }, + + /** + * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set + * + * @param {String} key Key to check + * @return {Number} Remaining TTL in milliseconds + */ + getTTL: function(key){ + var curtime = +new Date(), ttl; + _checkKey(key); + if(key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]){ + ttl = _storage.__jstorage_meta.TTL[key] - curtime; + return ttl || 0; + } + return 0; + }, + + /** * Deletes everything in cache. - * - * @returns true + * + * @return {Boolean} Always true */ flush: function(){ - _storage = {}; + _storage = {__jstorage_meta:{CRC32:{}}}; _save(); - /* - * Just to be sure - andris9/jStorage#3 - */ - try{ - window.localStorage.clear(); - }catch(E8){} + _publishChange(); + _fireObservers(null, "flushed"); return true; }, - + /** * Returns a read-only copy of _storage - * - * @returns Object + * + * @return {Object} Read-only copy of _storage */ storageObj: function(){ function F() {} F.prototype = _storage; return new F(); }, - + /** * Returns an index of all used keys as an array * ['key1', 'key2',..'keyN'] - * - * @returns Array + * + * @return {Array} Used keys */ index: function(){ var index = [], i; for(i in _storage){ - if(_storage.hasOwnProperty(i)){ + if(_storage.hasOwnProperty(i) && i != "__jstorage_meta"){ index.push(i); } } return index; }, - + /** * How much space in bytes does the storage take? - * - * @returns Number + * + * @return {Number} Storage size in chars (not the same as in bytes, + * since some chars may take several bytes) */ storageSize: function(){ return _storage_size; }, - + /** * Which backend is currently in use? - * - * @returns String + * + * @return {String} Backend name */ currentBackend: function(){ return _backend; }, - + /** * Test if storage is available - * - * @returns Boolean + * + * @return {Boolean} True if storage can be used */ storageAvailable: function(){ return !!_backend; }, - + + /** + * Register change listeners + * + * @param {String} key Key name + * @param {Function} callback Function to run when the key changes + */ + listenKeyChange: function(key, callback){ + _checkKey(key); + if(!_observers[key]){ + _observers[key] = []; + } + _observers[key].push(callback); + }, + + /** + * Remove change listeners + * + * @param {String} key Key name to unregister listeners against + * @param {Function} [callback] If set, unregister the callback, if not - unregister all + */ + stopListening: function(key, callback){ + _checkKey(key); + + if(!_observers[key]){ + return; + } + + if(!callback){ + delete _observers[key]; + return; + } + + for(var i = _observers[key].length - 1; i>=0; i--){ + if(_observers[key][i] == callback){ + _observers[key].splice(i,1); + } + } + }, + + /** + * Subscribe to a Publish/Subscribe event stream + * + * @param {String} channel Channel name + * @param {Function} callback Function to run when the something is published to the channel + */ + subscribe: function(channel, callback){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); + } + if(!_pubsub_observers[channel]){ + _pubsub_observers[channel] = []; + } + _pubsub_observers[channel].push(callback); + }, + + /** + * Publish data to an event stream + * + * @param {String} channel Channel name + * @param {Mixed} payload Payload to deliver + */ + publish: function(channel, payload){ + channel = (channel || "").toString(); + if(!channel){ + throw new TypeError('Channel not defined'); + } + + _publish(channel, payload); + }, + /** * Reloads the data from browser storage - * - * @returns undefined */ reInit: function(){ - var new_storage_elm, data; - if(_storage_elm && _storage_elm.addBehavior){ - new_storage_elm = document.createElement('link'); - - _storage_elm.parentNode.replaceChild(new_storage_elm, _storage_elm); - _storage_elm = new_storage_elm; - - /* Use a DOM element to act as userData storage */ - _storage_elm.style.behavior = 'url(#default#userData)'; - - /* userData element needs to be inserted into the DOM! */ - document.getElementsByTagName('head')[0].appendChild(_storage_elm); - - _storage_elm.load("jStorage"); - data = "{}"; - try{ - data = _storage_elm.getAttribute("jStorage"); - }catch(E5){} - _storage_service.jStorage = data; - _backend = "userDataBehavior"; - } - - _load_storage(); + _reloadData(); } }; // Initialize jStorage _init(); -})(window.jQuery || window.$); \ No newline at end of file +})(); diff -r c528daf3e3b3936dc399d6da4cfe4e636f4a556c -r 8ab88925e5eff54a056722bdf0fb3622d0a0455c static/scripts/mvc/base-mvc.js --- a/static/scripts/mvc/base-mvc.js +++ b/static/scripts/mvc/base-mvc.js @@ -196,10 +196,14 @@ storageDefaults = storageDefaults || {}; // ~constants for the current engine - //TODO:?? this would be greatly simplified if we're IE9+ only (setters/getters) - var STORAGE_ENGINE_GETTER = jQuery.jStorage.get, - STORAGE_ENGINE_SETTER = jQuery.jStorage.set, - STORAGE_ENGINE_KEY_DELETER = jQuery.jStorage.deleteKey; + var STORAGE_ENGINE = sessionStorage, + STORAGE_ENGINE_GETTER = function sessionStorageGet( key ){ + return JSON.parse( this.getItem( key ) ); + }, + STORAGE_ENGINE_SETTER = function sessionStorageSet( key, val ){ + return this.setItem( key, JSON.stringify( val ) ); + }, + STORAGE_ENGINE_KEY_DELETER = function sessionStorageDel( key ){ return this.removeItem( key ); }; /** Inner, recursive, private class for method chaining access. * @name StorageRecursionHelper @@ -258,18 +262,16 @@ //??: more readable to make another class? var returnedStorage = {}; // attempt to get starting data from engine... - data = STORAGE_ENGINE_GETTER( storageKey ); + data = STORAGE_ENGINE_GETTER.call( STORAGE_ENGINE, storageKey ); // ...if that fails, use the defaults (and store them) - if( data === null ){ - //console.debug( 'no previous data. using defaults...' ); + if( data === null || data === undefined ){ data = jQuery.extend( true, {}, storageDefaults ); - STORAGE_ENGINE_SETTER( storageKey, data ); + STORAGE_ENGINE_SETTER.call( STORAGE_ENGINE, storageKey, data ); } // the object returned by this constructor will be a modified StorageRecursionHelper returnedStorage = new StorageRecursionHelper( data ); - jQuery.extend( returnedStorage, /** @lends PersistantStorage.prototype */{ /** The base case for save()'s upward recursion - save everything to storage. * @private @@ -277,13 +279,13 @@ */ _save : function( newData ){ //console.debug( returnedStorage, '._save:', JSON.stringify( returnedStorage.get() ) ); - return STORAGE_ENGINE_SETTER( storageKey, returnedStorage.get() ); + return STORAGE_ENGINE_SETTER.call( STORAGE_ENGINE, storageKey, returnedStorage.get() ); }, /** Delete function to remove the entire base data object from the storageEngine. */ destroy : function(){ //console.debug( returnedStorage, '.destroy:' ); - return STORAGE_ENGINE_KEY_DELETER( storageKey ); + return STORAGE_ENGINE_KEY_DELETER.call( STORAGE_ENGINE, storageKey ); }, /** String representation. */ diff -r c528daf3e3b3936dc399d6da4cfe4e636f4a556c -r 8ab88925e5eff54a056722bdf0fb3622d0a0455c static/scripts/packed/mvc/base-mvc.js --- a/static/scripts/packed/mvc/base-mvc.js +++ b/static/scripts/packed/mvc/base-mvc.js @@ -1,1 +1,1 @@ -var BaseModel=Backbone.RelationalModel.extend({defaults:{name:null,hidden:false},show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},is_visible:function(){return !this.attributes.hidden}});var BaseView=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){if(this.model.attributes.hidden){this.$el.hide()}else{this.$el.show()}}});var LoggableMixin={logger:null,log:function(){if(this.logger){var a=this.logger.log;if(typeof this.logger.log==="object"){a=Function.prototype.bind.call(this.logger.log,this.logger)}return a.apply(this.logger,arguments)}return undefined}};var GalaxyLocalization=jQuery.extend({},{ALIAS_NAME:"_l",localizedStrings:{},setLocalizedString:function(b,a){var c=this;var d=function(f,e){if(f!==e){c.localizedStrings[f]=e}};if(jQuery.type(b)==="string"){d(b,a)}else{if(jQuery.type(b)==="object"){jQuery.each(b,function(e,f){d(e,f)})}else{throw ("Localization.setLocalizedString needs either a string or object as the first argument, given: "+b)}}},localize:function(a){return this.localizedStrings[a]||a},toString:function(){return"GalaxyLocalization"}});window[GalaxyLocalization.ALIAS_NAME]=function(a){return GalaxyLocalization.localize(a)};var PersistantStorage=function(g,d){if(!g){throw ("PersistantStorage needs storageKey argument")}d=d||{};var b=jQuery.jStorage.get,c=jQuery.jStorage.set,a=jQuery.jStorage.deleteKey;function e(i,h){i=i||{};h=h||null;return{get:function(j){if(j===undefined){return i}else{if(i.hasOwnProperty(j)){return(jQuery.type(i[j])==="object")?(new e(i[j],this)):(i[j])}}return undefined},set:function(j,k){i[j]=k;this._save();return this},deleteKey:function(j){delete i[j];this._save();return this},_save:function(){return h._save()},toString:function(){return("StorageRecursionHelper("+i+")")}}}var f={};data=b(g);if(data===null){data=jQuery.extend(true,{},d);c(g,data)}f=new e(data);jQuery.extend(f,{_save:function(h){return c(g,f.get())},destroy:function(){return a(g)},toString:function(){return"PersistantStorage("+g+")"}});return f}; \ No newline at end of file +var BaseModel=Backbone.RelationalModel.extend({defaults:{name:null,hidden:false},show:function(){this.set("hidden",false)},hide:function(){this.set("hidden",true)},is_visible:function(){return !this.attributes.hidden}});var BaseView=Backbone.View.extend({initialize:function(){this.model.on("change:hidden",this.update_visible,this);this.update_visible()},update_visible:function(){if(this.model.attributes.hidden){this.$el.hide()}else{this.$el.show()}}});var LoggableMixin={logger:null,log:function(){if(this.logger){var a=this.logger.log;if(typeof this.logger.log==="object"){a=Function.prototype.bind.call(this.logger.log,this.logger)}return a.apply(this.logger,arguments)}return undefined}};var GalaxyLocalization=jQuery.extend({},{ALIAS_NAME:"_l",localizedStrings:{},setLocalizedString:function(b,a){var c=this;var d=function(f,e){if(f!==e){c.localizedStrings[f]=e}};if(jQuery.type(b)==="string"){d(b,a)}else{if(jQuery.type(b)==="object"){jQuery.each(b,function(e,f){d(e,f)})}else{throw ("Localization.setLocalizedString needs either a string or object as the first argument, given: "+b)}}},localize:function(a){return this.localizedStrings[a]||a},toString:function(){return"GalaxyLocalization"}});window[GalaxyLocalization.ALIAS_NAME]=function(a){return GalaxyLocalization.localize(a)};var PersistantStorage=function(j,g){if(!j){throw ("PersistantStorage needs storageKey argument")}g=g||{};var h=sessionStorage,c=function i(l){return JSON.parse(this.getItem(l))},b=function e(l,m){return this.setItem(l,JSON.stringify(m))},d=function f(l){return this.removeItem(l)};function a(m,l){m=m||{};l=l||null;return{get:function(n){if(n===undefined){return m}else{if(m.hasOwnProperty(n)){return(jQuery.type(m[n])==="object")?(new a(m[n],this)):(m[n])}}return undefined},set:function(n,o){m[n]=o;this._save();return this},deleteKey:function(n){delete m[n];this._save();return this},_save:function(){return l._save()},toString:function(){return("StorageRecursionHelper("+m+")")}}}var k={};data=c.call(h,j);if(data===null||data===undefined){data=jQuery.extend(true,{},g);b.call(h,j,data)}k=new a(data);jQuery.extend(k,{_save:function(l){return b.call(h,j,k.get())},destroy:function(){return d.call(h,j)},toString:function(){return"PersistantStorage("+j+")"}});return k}; \ No newline at end of file Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.