Ext.namespace('Ext.ux'); (function(){ var log; if ( window.console ) { log = function(m) { window.console.log(m); }; } else if ( Ext.log ) { log = window.Ext.log; } else { log = Ext.emptyFn; } Ext.ux.InterWindowPubSub = function( config ) { this.storageKey = '__storageKey__'; this.chunkSize = 2000; Ext.apply( this, config ); if ( !this.domain ) { /* var l = location.hostname.split('.').reverse(); this.domain = l[ 0 ]; if ( l[ 1 ] ) this.domain = l[ 1 ] + '.' + this.domain; */ this.domain = location.hostname; } this.init.apply( this, arguments ); }; Ext.extend( Ext.ux.InterWindowPubSub, Ext.util.Observable, { version: '1.0', init: function() { this.id = 'win-' + parseInt( Math.random() * 100000000000 ); this.queue = []; this.buffer = []; this.addEvents({ message: true }); log('domain for inter-window pubsub: '+this.domain); if ( Ext.isIE ) { log('loading IE storage'); this.chunkSize = 2000; this.useACK = true; this.provider = new Ext.ux.IEPersistentStorageProvider({ domain: this.domain }); } else if ( Ext.isGecko ) { // XXX IE8 beta? log('loading Firefox storage'); this.chunkSize = 2000; this.useACK = false; this.provider = new Ext.ux.DOMStorageProvider({ domain: this.domain }); } else { log('loading Generic storage'); // XXX cookie expiration this.chunkSize = 500; this.useACK = true; this.provider = new Ext.state.CookieProvider({ path: '/', // expires: new Date(new Date().getTime()+(1000*60*60*24*30)), // 30 days domain: this.domain }); } this.provider.on( 'statechange', this.stateChange, this ); }, sendMessage: function(msg) { if ( ( msg instanceof Array ) || ( msg instanceof Object ) ) { log('auto converted object using json'); msg = 'JSON\u0000'+Ext.encode( msg ); } else { // stringify msg += ''; } this.queue.push( msg ); this.checkQueue(); }, checkQueue: function() { if ( this.work ) { // this.chunkSize = 7; if ( !( this.work instanceof Array ) ) { log('working on chunk, chunk size:'+this.chunkSize); var w = this.work; this.work = []; log('splitting:['+w+']'); for ( var i = 0, len = w.length; i < len; i+= this.chunkSize ) { this.work.push( w.substr( i, this.chunkSize ) ); } log(this.work); this.workLength = this.work.length; if ( this.workLength == 1 ) { log('sending single chunk'); this.provider.set( this.storageKey, this.work.shift() ); } this.lastACK = null; } if ( this.work.length == 0 ) { this.work = null; this.checkQueue.defer( 10, this ); return; } var chunk = this.work.shift(); this.ackChunk = this.workLength - this.work.length; log('sending chunk:'+n); this.provider.set( this.storageKey, '\u0000'+this.ackChunk+','+this.workLength+'\u0000'+chunk ); this.checkQueue.defer( 1, this ); return; } if ( this.queue.length == 0 ) return; log('shifting work'); this.work = this.queue.shift(); this.checkQueue.defer( 1, this ); }, stateChange: function() { var data = this.provider.get( this.storageKey, null ); if ( data === null ) return; log('state change'); // log(data); if ( data.match( /^(\u0000(\d+),(\d+)\u0000)/ ) ) { var i = parseInt( RegExp.$2 ); var len = parseInt( RegExp.$3 ); log('found chunked data'); if ( i == 1 ) this.buffer = []; if ( i > 1 && this.buffer[i-1] === undefined ) { log('received tail end of chunked send, ignoring'); return; } this.buffer[i] = data.substr( RegExp.$1.length ); log('buffer:'+this.buffer[i]); if ( i == len ) { this.dataReceived.defer( 1, this, [ this.buffer.join( '' ) ] ); this.buffer = []; } return; } this.dataReceived.defer( 1, this, [ data ] ); }, dataReceived: function( data ) { if ( data.match( /^JSON\u0000/ ) ) { log('auto decoding json'); data = Ext.decode( data.substr( 5 ) ); log(data); } this.fireEvent( 'message', this, data ); } }); Ext.ux.DOMStorageProvider = function(config){ Ext.ux.DOMStorageProvider.superclass.constructor.call(this); this.domain = null; this.keyChangeKey = '__keyChanged'; Ext.apply( this, config ); if ( !this.domain ) throw 'Ext.ux.DOMStorage requires a domain'; this.init.apply( this, arguments ); }; Ext.extend(Ext.ux.DOMStorageProvider, Ext.state.Provider, { init: function() { this.state = {}; this.addEvents({ statechange: true }); if ( window.localStorage ) { log('using localStorage'); this.storage = window.localStorage[ this.domain ]; } else if ( window.globalStorage ) { log('using globalStorage'); this.storage = window.globalStorage[ this.domain ]; } else { log('using sessionStorage'); this.storage = window.sessionStorage; } Ext.EventManager.on( document, 'storage', this.storageEvent, this ); }, storageEvent: function(ev) { var keyChanged = this.get( this.keyChangeKey, null ); if ( keyChanged !== null ) { if ( this.keyChanged !== keyChanged ) { this.keyChanged = keyChanged; this.valueChanged = this.get( keyChanged ); log('storage event, key changed:'+keyChanged); return; } } // XXX the original value of the last this.fireEvent( 'statechange', this, this.keyChanged, this.get( this.keyChanged ) ); this.keyChanged = null; this.valueChanged = null; }, get: function( key, defaultValue ) { var v = this.storage.getItem( key ); if ( v && typeof v == 'object' ) return v.value; return defaultValue; }, set: function( key, value ) { if (typeof value == 'undefined' || value === null) { this.clear( key ); return; } // this will fire two events, one for what key changed, and the other for the real value this.keyChanged = null; this.valueChanged = null; this.storage.setItem( this.keyChangeKey, key ); return this.storage.setItem( key, value ); }, // NOT the same as DOMStorage clear() clear: function( key ) { this.keyChanged = null; this.valueChanged = null; this.storage.setItem( this.keyChangeKey, key ); return this.storage.removeItem( key ); } }); Ext.ux.IEPersistentStorageProvider = function( config ) { Ext.ux.IEPersistentStorageProvider.superclass.constructor.call(this); this.domain = null; this.storeName = 'iwcStore'; Ext.apply( this, config ); /// XXX does it really? // if ( !this.domain ) // throw 'Ext.ux.IEPersistentStorageProvider requires a domain'; this.init.apply( this, arguments ); }; /* IE 5.1+ */ Ext.extend( Ext.ux.IEPersistentStorageProvider, Ext.util.Observable, { init: function() { this.state = {}; this.addEvents({ statechange: true }); this.items = {}; this.itemlist = []; this.timer = this.checkValue.defer( 150, this ); }, checkValue: function() { this.timer = this.checkValue.defer( 150, this ); if ( !this.itemlist.length ) return; var l = this.itemlist.length; for ( var i = 0; i < l; i++ ) { var key = this.itemlist[ i ]; var storage = this.items[ key ]; var va = storage.getAttribute( key ); try { storage.load( this.storeName ); } catch( e ) { log( e ); }; var vv = storage.getAttribute( key ); if ( va != vv ) this.fireEvent( 'statechange', this, key, vv ); } }, getStorage: function( key, create ) { /* XXX required */ var storage; if ( this.items.hasOwnProperty( key ) ) storage = this.items[ key ]; else { if ( !create ) return null; storage = document.createElement( 'input' ); storage.setAttribute( 'type', 'hidden' ); storage.addBehavior ( '#default#userData' ); storage.className = 'userData'; // XXX encode this key? storage.setAttribute( 'id', 'storage-' + this.encodeValue( key ) ); document.body.appendChild( storage ); this.items[ key ] = storage; this.itemlist.push( key ); } return storage; }, get: function( key, defaultValue ) { /// XXX create flag? var storage = this.getStorage( key, true ); if ( !storage ) { log('could not get storage'); return null; } while( 1 ) { /* IE doesn't lock thread access to storage * it just throws an error instead */ try { storage.load( this.storeName ); } catch( e ) { log( e ); continue; }; break; } var v = storage.getAttribute( key ); return typeof v == 'undefined' || v === null ? defaultValue : v; }, set: function( key, value ) { if (typeof value == 'undefined' || value === null) { this.clear( key ); return; } var storage = this.getStorage( key, true ); if ( !storage ) return log('could not get storage'); storage.setAttribute( key, value ); while( 1 ) { /* IE doesn't lock thread access to storage * it just throws an error instead */ try { storage.save( this.storeName ); } catch( e ) { log( e ); continue; }; break; } this.fireEvent( 'statechange', this, key, value ); return; }, clear: function( key ) { this.set( key, undefined ); } }); })();