UNPKG

golden-layout

Version:

A multi-screen javascript Layout manager https://golden-layout.com

255 lines (225 loc) 7.17 kB
/** * Pops a content item out into a new browser window. * This is achieved by * * - Creating a new configuration with the content item as root element * - Serializing and minifying the configuration * - Opening the current window's URL with the configuration as a GET parameter * - GoldenLayout when opened in the new window will look for the GET parameter * and use it instead of the provided configuration * * @param {Object} config GoldenLayout item config * @param {Object} dimensions A map with width, height, top and left * @param {String} parentId The id of the element the item will be appended to on popIn * @param {Number} indexInParent The position of this element within its parent * @param {lm.LayoutManager} layoutManager */ lm.controls.BrowserPopout = function( config, dimensions, parentId, indexInParent, layoutManager ) { lm.utils.EventEmitter.call( this ); this.isInitialised = false; this._config = config; this._dimensions = dimensions; this._parentId = parentId; this._indexInParent = indexInParent; this._layoutManager = layoutManager; this._popoutWindow = null; this._id = null; this._createWindow(); }; lm.utils.copy( lm.controls.BrowserPopout.prototype, { toConfig: function() { return { dimensions:{ width: this.getGlInstance().width, height: this.getGlInstance().height, left: this._popoutWindow.screenX || this._popoutWindow.screenLeft, top: this._popoutWindow.screenY || this._popoutWindow.screenTop }, content: this.getGlInstance().toConfig().content, parentId: this._parentId, indexInParent: this._indexInParent }; }, getGlInstance: function() { return this._popoutWindow.__glInstance; }, getWindow: function() { return this._popoutWindow; }, close: function() { if( this.getGlInstance() ) { this.getGlInstance()._$closeWindow(); } else { try{ this.getWindow().close(); } catch( e ){} } }, /** * Returns the popped out item to its original position. If the original * parent isn't available anymore it falls back to the layout's topmost element */ popIn: function() { var childConfig, parentItem, index = this._indexInParent; if( this._parentId ) { /* * The $.extend call seems a bit pointless, but it's crucial to * copy the config returned by this.getGlInstance().toConfig() * onto a new object. Internet Explorer keeps the references * to objects on the child window, resulting in the following error * once the child window is closed: * * The callee (server [not server application]) is not available and disappeared */ childConfig = $.extend( true, {}, this.getGlInstance().toConfig() ).content[ 0 ]; parentItem = this._layoutManager.root.getItemsById( this._parentId )[ 0 ]; /* * Fallback if parentItem is not available. Either add it to the topmost * item or make it the topmost item if the layout is empty */ if( !parentItem ) { if( this._layoutManager.root.contentItems.length > 0 ) { parentItem = this._layoutManager.root.contentItems[ 0 ]; } else { parentItem = this._layoutManager.root; } index = 0; } } parentItem.addChild( childConfig, this._indexInParent ); this.close(); }, /** * Creates the URL and window parameter * and opens a new window * * @private * * @returns {void} */ _createWindow: function() { var checkReadyInterval, url = this._createUrl(), /** * Bogus title to prevent re-usage of existing window with the * same title. The actual title will be set by the new window's * GoldenLayout instance if it detects that it is in subWindowMode */ title = Math.floor( Math.random() * 1000000 ).toString( 36 ), /** * The options as used in the window.open string */ options = this._serializeWindowOptions({ width: this._dimensions.width, height: this._dimensions.height, innerWidth: this._dimensions.width, innerHeight: this._dimensions.height, menubar: 'no', toolbar: 'no', location: 'no', personalbar: 'no', resizable: 'yes', scrollbars: 'no', status: 'no' }); this._popoutWindow = window.open( url, title, options ); if( !this._popoutWindow ) { if( this._layoutManager.config.settings.blockedPopoutsThrowError === true ) { var error = new Error( 'Popout blocked' ); error.type = 'popoutBlocked'; throw error; } else { return; } } $( this._popoutWindow ) .on( 'load', lm.utils.fnBind( this._positionWindow, this ) ) .on( 'unload beforeunload', lm.utils.fnBind( this._onClose, this ) ); /** * Polling the childwindow to find out if GoldenLayout has been initialised * doesn't seem optimal, but the alternatives - adding a callback to the parent * window or raising an event on the window object - both would introduce knowledge * about the parent to the child window which we'd rather avoid */ checkReadyInterval = setInterval(lm.utils.fnBind(function(){ if( this._popoutWindow.__glInstance && this._popoutWindow.__glInstance.isInitialised ) { this._onInitialised(); clearInterval( checkReadyInterval ); } }, this ), 10 ); }, /** * Serialises a map of key:values to a window options string * * @param {Object} windowOptions * * @returns {String} serialised window options */ _serializeWindowOptions: function( windowOptions ) { var windowOptionsString = [], key; for( key in windowOptions ) { windowOptionsString.push( key + '=' + windowOptions[ key ] ); } return windowOptionsString.join( ',' ); }, /** * Creates the URL for the new window, including the * config GET parameter * * @returns {String} URL */ _createUrl: function() { var config = { content: this._config }, storageKey = 'gl-window-config-' + lm.utils.getUniqueId(), urlParts; config = ( new lm.utils.ConfigMinifier() ).minifyConfig( config ); try{ localStorage.setItem( storageKey, JSON.stringify( config ) ); } catch( e ) { throw new Error( 'Error while writing to localStorage ' + e.toString() ); } urlParts = document.location.href.split( '?' ); // URL doesn't contain GET-parameters if( urlParts.length === 1 ) { return urlParts[ 0 ] + '?gl-window=' + storageKey; // URL contains GET-parameters } else { return document.location.href + '&gl-window=' + storageKey; } }, /** * Move the newly created window roughly to * where the component used to be. * * @private * * @returns {void} */ _positionWindow: function() { this._popoutWindow.moveTo( this._dimensions.left, this._dimensions.top ); this._popoutWindow.focus(); }, /** * Callback when the new window is opened and the GoldenLayout instance * within it is initialised * * @returns {void} */ _onInitialised: function() { this.isInitialised = true; this.getGlInstance().on( 'popIn', this.popIn, this ); this.emit( 'initialised' ); }, /** * Invoked 50ms after the window unload event * * @private * * @returns {void} */ _onClose: function() { setTimeout( lm.utils.fnBind( this.emit, this, [ 'closed' ] ), 50 ); } });