golden-layout
Advanced tools
Comparing version 2.3.0 to 2.4.0
@@ -189,2 +189,19 @@ "use strict"; | ||
this.updateElementPositionPropertyFromBoundComponent(); | ||
if (this._boundComponent.virtual) { | ||
if (this.virtualVisibilityChangeRequiredEvent !== undefined) { | ||
this.virtualVisibilityChangeRequiredEvent(this, this._visible); | ||
} | ||
if (this.virtualRectingRequiredEvent !== undefined) { | ||
this._layoutManager.fireBeforeVirtualRectingEvent(1); | ||
try { | ||
this.virtualRectingRequiredEvent(this, this._width, this._height); | ||
} | ||
finally { | ||
this._layoutManager.fireAfterVirtualRectingEvent(); | ||
} | ||
} | ||
if (this.virtualZIndexChangeRequiredEvent !== undefined) { | ||
this.virtualZIndexChangeRequiredEvent(this, types_1.LogicalZIndex.base, style_constants_1.StyleConstants.defaultComponentBaseZIndex); | ||
} | ||
} | ||
this.emit('stateChanged'); | ||
@@ -191,0 +208,0 @@ } |
@@ -261,11 +261,5 @@ "use strict"; | ||
} | ||
const 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; | ||
} | ||
const url = new URL(location.href); | ||
url.searchParams.set('gl-window', storageKey); | ||
return url.toString(); | ||
} | ||
@@ -272,0 +266,0 @@ /** |
@@ -10,2 +10,3 @@ "use strict"; | ||
const drag_proxy_1 = require("./drag-proxy"); | ||
const resolved_config_1 = require("../config/resolved-config"); | ||
/** | ||
@@ -38,7 +39,5 @@ * Allows for any DOM item to create a component on drag | ||
this._dragListener = null; | ||
// Need to review dummyGroundContainer | ||
// Should this part of a fragment or template? | ||
// Does this need to be regenerated with each drag operation? | ||
this._dummyGroundContainer = document.createElement('div'); | ||
this._dummyGroundContentItem = new ground_item_1.GroundItem(this._layoutManager, this._layoutManager.layoutConfig.root, this._dummyGroundContainer); | ||
const dummyRootItemConfig = resolved_config_1.ResolvedRowOrColumnItemConfig.createDefault('row'); | ||
this._dummyGroundContentItem = new ground_item_1.GroundItem(this._layoutManager, dummyRootItemConfig, this._dummyGroundContainer); | ||
this.createDragListener(); | ||
@@ -45,0 +44,0 @@ } |
@@ -87,3 +87,5 @@ "use strict"; | ||
this._tabControlOffset = this._layoutManager.layoutConfig.settings.tabControlOffset; | ||
this._tabDropdownButton = new header_button_1.HeaderButton(this, this._tabDropdownLabel, "lm_tabdropdown" /* TabDropdown */, () => this._tabsContainer.showAdditionalTabsDropdown()); | ||
if (this._tabDropdownEnabled) { | ||
this._tabDropdownButton = new header_button_1.HeaderButton(this, this._tabDropdownLabel, "lm_tabdropdown" /* TabDropdown */, () => this._tabsContainer.showAdditionalTabsDropdown()); | ||
} | ||
if (this._popoutEnabled) { | ||
@@ -108,7 +110,4 @@ this._popoutButton = new header_button_1.HeaderButton(this, this._popoutLabel, "lm_popout" /* Popout */, () => this.handleButtonPopoutEvent()); | ||
// private _activeComponentItem: ComponentItem | null = null; // only used to identify active tab | ||
/** @internal */ | ||
get show() { return this._show; } | ||
/** @internal */ | ||
get side() { return this._side; } | ||
/** @internal */ | ||
get leftRightSided() { return this._leftRightSided; } | ||
@@ -119,20 +118,5 @@ get layoutManager() { return this._layoutManager; } | ||
get lastVisibleTabIndex() { return this._tabsContainer.lastVisibleTabIndex; } | ||
/** | ||
* @deprecated use {@link (Stack:class).getActiveComponentItem} */ | ||
get activeContentItem() { | ||
const activeComponentItem = this._getActiveComponentItemEvent(); | ||
if (activeComponentItem === undefined) { | ||
return null; | ||
} | ||
else { | ||
return activeComponentItem; | ||
} | ||
} | ||
get element() { return this._element; } | ||
/** @deprecated use {@link (Header:class).tabsContainerElement} */ | ||
get tabsContainer() { return this._tabsContainer.element; } | ||
get tabsContainerElement() { return this._tabsContainer.element; } | ||
get controlsContainerElement() { return this._controlsContainerElement; } | ||
/** @deprecated use {@link (Header:class).controlsContainerElement} */ | ||
get controlsContainer() { return this._controlsContainerElement; } | ||
/** | ||
@@ -315,3 +299,5 @@ * Destroys the entire header | ||
processTabDropdownActiveChanged() { | ||
utils_1.setElementDisplayVisibility(this._tabDropdownButton.element, this._tabsContainer.dropdownActive); | ||
if (this._tabDropdownButton !== undefined) { | ||
utils_1.setElementDisplayVisibility(this._tabDropdownButton.element, this._tabsContainer.dropdownActive); | ||
} | ||
} | ||
@@ -318,0 +304,0 @@ /** @internal */ |
@@ -12,4 +12,5 @@ "use strict"; | ||
class GoldenLayout extends virtual_layout_1.VirtualLayout { | ||
constructor() { | ||
super(...arguments); | ||
/** @internal */ | ||
constructor(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler) { | ||
super(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler, true); | ||
/** @internal */ | ||
@@ -25,2 +26,6 @@ this._componentTypesMap = new Map(); | ||
this._containerVirtualZIndexChangeRequiredEventListener = (container, logicalZIndex, defaultZIndex) => this.handleContainerVirtualZIndexChangeRequiredEvent(container, logicalZIndex, defaultZIndex); | ||
// we told VirtualLayout to not call init() (skipInit set to true) so that Golden Layout can initialise its properties before init is called | ||
if (!this.deprecatedConstructor) { | ||
this.init(); | ||
} | ||
} | ||
@@ -27,0 +32,0 @@ /** |
@@ -47,2 +47,3 @@ "use strict"; | ||
get id() { return this._id; } | ||
set id(value) { this._id = value; } | ||
/** @internal */ | ||
@@ -49,0 +50,0 @@ get popInParentIds() { return this._popInParentIds; } |
@@ -81,2 +81,3 @@ "use strict"; | ||
get childElementContainer() { return this._childElementContainer; } | ||
get header() { return this._header; } | ||
get headerShow() { return this._header.show; } | ||
@@ -83,0 +84,0 @@ get headerSide() { return this._header.side; } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getUniqueId = exports.removeFromArray = exports.deepExtendValue = exports.deepExtend = exports.extend = exports.ensureElementPositionAbsolute = exports.setElementDisplayVisibility = exports.getElementWidthAndHeight = exports.setElementHeight = exports.getElementHeight = exports.setElementWidth = exports.getElementWidth = exports.pixelsToNumber = exports.numberToPixels = exports.getQueryStringParam = void 0; | ||
exports.getUniqueId = exports.removeFromArray = exports.deepExtendValue = exports.deepExtend = exports.extend = exports.ensureElementPositionAbsolute = exports.setElementDisplayVisibility = exports.getElementWidthAndHeight = exports.setElementHeight = exports.getElementHeight = exports.setElementWidth = exports.getElementWidth = exports.pixelsToNumber = exports.numberToPixels = void 0; | ||
/** @internal */ | ||
function getQueryStringParam(key) { | ||
const matches = location.search.match(new RegExp(key + '=([^&]*)')); | ||
return matches ? matches[1] : null; | ||
} | ||
exports.getQueryStringParam = getQueryStringParam; | ||
/** @internal */ | ||
function numberToPixels(value) { | ||
@@ -12,0 +6,0 @@ return value.toString(10) + 'px'; |
@@ -10,16 +10,15 @@ "use strict"; | ||
const i18n_strings_1 = require("./utils/i18n-strings"); | ||
const utils_1 = require("./utils/utils"); | ||
/** @public */ | ||
class VirtualLayout extends layout_manager_1.LayoutManager { | ||
/** @internal */ | ||
constructor(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler) { | ||
constructor(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler, skipInit) { | ||
super(VirtualLayout.createLayoutManagerConstructorParameters(configOrOptionalContainer, containerOrBindComponentEventHandler)); | ||
/** @internal */ | ||
this._subWindowsCreated = false; | ||
/** @internal */ | ||
this._creationTimeoutPassed = false; | ||
// More work needed to get popouts working with virtual. | ||
/** @internal @deprecated use while constructor is not determinate */ | ||
this._bindComponentEventHanlderPassedInConstructor = false; // remove when constructor is determinate | ||
/** @internal @deprecated use while constructor is not determinate */ | ||
this._creationTimeoutPassed = false; // remove when constructor is determinate | ||
if (containerOrBindComponentEventHandler !== undefined) { | ||
if (typeof containerOrBindComponentEventHandler === 'function') { | ||
this.bindComponentEvent = containerOrBindComponentEventHandler; | ||
this._bindComponentEventHanlderPassedInConstructor = true; | ||
if (unbindComponentEventHandler !== undefined) { | ||
@@ -30,7 +29,23 @@ this.unbindComponentEvent = unbindComponentEventHandler; | ||
} | ||
if (this.isSubWindow) { | ||
document.body.style.visibility = 'hidden'; | ||
if (!this._bindComponentEventHanlderPassedInConstructor) { | ||
// backward compatibility | ||
if (this.isSubWindow) { | ||
// document.body.style.visibility = 'hidden'; | ||
// Set up layoutConfig since constructor is not determinate and may exit early. Other functions may need | ||
// this.layoutConfig. this.layoutConfig is again calculated in the same way when init() completes. | ||
// Remove this when constructor is determinate. | ||
if (this._constructorOrSubWindowLayoutConfig === undefined) { | ||
throw new internal_error_1.UnexpectedUndefinedError('VLC98823'); | ||
} | ||
else { | ||
const resolvedLayoutConfig = config_1.LayoutConfig.resolve(this._constructorOrSubWindowLayoutConfig); | ||
// remove root from layoutConfig | ||
this.layoutConfig = Object.assign(Object.assign({}, resolvedLayoutConfig), { root: undefined }); | ||
} | ||
} | ||
} | ||
if (this.layoutConfig.root === undefined || this.isSubWindow) { | ||
this.init(); | ||
if (skipInit !== true) { | ||
if (!this.deprecatedConstructor) { | ||
this.init(); | ||
} | ||
} | ||
@@ -56,14 +71,5 @@ } | ||
/** | ||
* Create the popout windows straight away. If popouts are blocked | ||
* an error is thrown on the same 'thread' rather than a timeout and can | ||
* be caught. This also prevents any further initilisation from taking place. | ||
*/ | ||
if (this._subWindowsCreated === false) { | ||
this.createSubWindows(); | ||
this._subWindowsCreated = true; | ||
} | ||
/** | ||
* If the document isn't ready yet, wait for it. | ||
*/ | ||
if (document.readyState === 'loading' || document.body === null) { | ||
if (!this._bindComponentEventHanlderPassedInConstructor && (document.readyState === 'loading' || document.body === null)) { | ||
document.addEventListener('DOMContentLoaded', () => this.init(), { passive: true }); | ||
@@ -77,3 +83,3 @@ return; | ||
*/ | ||
if (this.isSubWindow === true && this._creationTimeoutPassed === false) { | ||
if (!this._bindComponentEventHanlderPassedInConstructor && this.isSubWindow === true && !this._creationTimeoutPassed) { | ||
setTimeout(() => this.init(), 7); | ||
@@ -84,6 +90,68 @@ this._creationTimeoutPassed = true; | ||
if (this.isSubWindow === true) { | ||
this.adjustToWindowMode(); | ||
if (!this._bindComponentEventHanlderPassedInConstructor) { | ||
this.clearHtmlAndAdjustStylesForSubWindow(); | ||
} | ||
// Expose this instance on the window object to allow the opening window to interact with it | ||
window.__glInstance = this; | ||
} | ||
super.init(); | ||
} | ||
/** | ||
* Clears existing HTML and adjusts style to make window suitable to be a popout sub window | ||
* Curently is automatically called when window is a subWindow and bindComponentEvent is not passed in the constructor | ||
* If bindComponentEvent is not passed in the constructor, the application must either call this function explicitly or | ||
* (preferably) make the window suitable as a subwindow. | ||
* In the future, it is planned that this function is NOT automatically called in any circumstances. Applications will | ||
* need to determine whether a window is a Golden Layout popout window and either call this function explicitly or | ||
* hide HTML not relevant to the popout. | ||
* See apitest for an example of how HTML is hidden when popout windows are displayed | ||
*/ | ||
clearHtmlAndAdjustStylesForSubWindow() { | ||
const headElement = document.head; | ||
const appendNodeLists = new Array(4); | ||
appendNodeLists[0] = document.querySelectorAll('body link'); | ||
appendNodeLists[1] = document.querySelectorAll('body style'); | ||
appendNodeLists[2] = document.querySelectorAll('template'); | ||
appendNodeLists[3] = document.querySelectorAll('.gl_keep'); | ||
for (let listIdx = 0; listIdx < appendNodeLists.length; listIdx++) { | ||
const appendNodeList = appendNodeLists[listIdx]; | ||
for (let nodeIdx = 0; nodeIdx < appendNodeList.length; nodeIdx++) { | ||
const node = appendNodeList[nodeIdx]; | ||
headElement.appendChild(node); | ||
} | ||
} | ||
const bodyElement = document.body; | ||
bodyElement.innerHTML = ''; | ||
bodyElement.style.visibility = 'visible'; | ||
this.checkAddDefaultPopinButton(); | ||
/* | ||
* This seems a bit pointless, but actually causes a reflow/re-evaluation getting around | ||
* slickgrid's "Cannot find stylesheet." bug in chrome | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const x = document.body.offsetHeight; | ||
} | ||
/** | ||
* Will add button if not popinOnClose specified in settings | ||
* @returns true if added otherwise false | ||
*/ | ||
checkAddDefaultPopinButton() { | ||
if (this.layoutConfig.settings.popInOnClose) { | ||
return false; | ||
} | ||
else { | ||
const popInButtonElement = document.createElement('div'); | ||
popInButtonElement.classList.add("lm_popin" /* Popin */); | ||
popInButtonElement.setAttribute('title', this.layoutConfig.header.dock); | ||
const iconElement = document.createElement('div'); | ||
iconElement.classList.add("lm_icon" /* Icon */); | ||
const bgElement = document.createElement('div'); | ||
bgElement.classList.add("lm_bg" /* Bg */); | ||
popInButtonElement.appendChild(iconElement); | ||
popInButtonElement.appendChild(bgElement); | ||
popInButtonElement.addEventListener('click', () => this.emit('popIn')); | ||
document.body.appendChild(popInButtonElement); | ||
return true; | ||
} | ||
} | ||
/** @internal */ | ||
@@ -129,61 +197,2 @@ bindComponent(container, itemConfig) { | ||
} | ||
/** | ||
* Creates Subwindows (if there are any). Throws an error | ||
* if popouts are blocked. | ||
* @internal | ||
*/ | ||
createSubWindows() { | ||
for (let i = 0; i < this.layoutConfig.openPopouts.length; i++) { | ||
const popoutConfig = this.layoutConfig.openPopouts[i]; | ||
this.createPopoutFromPopoutLayoutConfig(popoutConfig); | ||
} | ||
} | ||
/** | ||
* This is executed when GoldenLayout detects that it is run | ||
* within a previously opened popout window. | ||
* @internal | ||
*/ | ||
adjustToWindowMode() { | ||
const headElement = document.head; | ||
const appendNodeLists = new Array(4); | ||
appendNodeLists[0] = document.querySelectorAll('body link'); | ||
appendNodeLists[1] = document.querySelectorAll('body style'); | ||
appendNodeLists[2] = document.querySelectorAll('template'); | ||
appendNodeLists[3] = document.querySelectorAll('.gl_keep'); | ||
for (let listIdx = 0; listIdx < appendNodeLists.length; listIdx++) { | ||
const appendNodeList = appendNodeLists[listIdx]; | ||
for (let nodeIdx = 0; nodeIdx < appendNodeList.length; nodeIdx++) { | ||
const node = appendNodeList[nodeIdx]; | ||
headElement.appendChild(node); | ||
} | ||
} | ||
const bodyElement = document.body; | ||
bodyElement.innerHTML = ''; | ||
bodyElement.style.visibility = 'visible'; | ||
if (!this.layoutConfig.settings.popInOnClose) { | ||
const popInButtonElement = document.createElement('div'); | ||
popInButtonElement.classList.add("lm_popin" /* Popin */); | ||
popInButtonElement.setAttribute('title', this.layoutConfig.header.dock); | ||
const iconElement = document.createElement('div'); | ||
iconElement.classList.add("lm_icon" /* Icon */); | ||
const bgElement = document.createElement('div'); | ||
bgElement.classList.add("lm_bg" /* Bg */); | ||
popInButtonElement.appendChild(iconElement); | ||
popInButtonElement.appendChild(bgElement); | ||
popInButtonElement.addEventListener('click', () => this.emit('popIn')); | ||
bodyElement.appendChild(popInButtonElement); | ||
} | ||
/* | ||
* This seems a bit pointless, but actually causes a reflow/re-evaluation getting around | ||
* slickgrid's "Cannot find stylesheet." bug in chrome | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const x = document.body.offsetHeight; | ||
/* | ||
* Expose this instance on the window object | ||
* to allow the opening window to interact with | ||
* it | ||
*/ | ||
window.__glInstance = this; | ||
} | ||
} | ||
@@ -200,3 +209,3 @@ exports.VirtualLayout = VirtualLayout; | ||
function createLayoutManagerConstructorParameters(configOrOptionalContainer, containerOrBindComponentEventHandler) { | ||
const windowConfigKey = subWindowChecked ? null : utils_1.getQueryStringParam('gl-window'); | ||
const windowConfigKey = subWindowChecked ? null : new URL(document.location.href).searchParams.get('gl-window'); | ||
subWindowChecked = true; | ||
@@ -213,3 +222,7 @@ const isSubWindow = windowConfigKey !== null; | ||
const minifiedWindowConfig = JSON.parse(windowConfigStr); | ||
config = resolved_config_1.ResolvedLayoutConfig.unminifyConfig(minifiedWindowConfig); | ||
const resolvedConfig = resolved_config_1.ResolvedLayoutConfig.unminifyConfig(minifiedWindowConfig); | ||
config = config_1.LayoutConfig.fromResolved(resolvedConfig); | ||
if (configOrOptionalContainer instanceof HTMLElement) { | ||
containerElement = configOrOptionalContainer; | ||
} | ||
} | ||
@@ -226,8 +239,4 @@ else { | ||
else { | ||
if (config_1.LayoutConfig.isResolved(configOrOptionalContainer)) { | ||
config = configOrOptionalContainer; | ||
} | ||
else { | ||
config = config_1.LayoutConfig.resolve(configOrOptionalContainer); | ||
} | ||
// backwards compatibility | ||
config = configOrOptionalContainer; | ||
} | ||
@@ -242,3 +251,3 @@ } | ||
return { | ||
layoutConfig: config, | ||
constructorOrSubWindowLayoutConfig: config, | ||
isSubWindow, | ||
@@ -245,0 +254,0 @@ containerElement, |
@@ -186,2 +186,19 @@ import { ComponentItemConfig, ItemConfig } from '../config/config'; | ||
this.updateElementPositionPropertyFromBoundComponent(); | ||
if (this._boundComponent.virtual) { | ||
if (this.virtualVisibilityChangeRequiredEvent !== undefined) { | ||
this.virtualVisibilityChangeRequiredEvent(this, this._visible); | ||
} | ||
if (this.virtualRectingRequiredEvent !== undefined) { | ||
this._layoutManager.fireBeforeVirtualRectingEvent(1); | ||
try { | ||
this.virtualRectingRequiredEvent(this, this._width, this._height); | ||
} | ||
finally { | ||
this._layoutManager.fireAfterVirtualRectingEvent(); | ||
} | ||
} | ||
if (this.virtualZIndexChangeRequiredEvent !== undefined) { | ||
this.virtualZIndexChangeRequiredEvent(this, LogicalZIndex.base, StyleConstants.defaultComponentBaseZIndex); | ||
} | ||
} | ||
this.emit('stateChanged'); | ||
@@ -188,0 +205,0 @@ } |
@@ -258,11 +258,5 @@ import { ResolvedLayoutConfig } from '../config/resolved-config'; | ||
} | ||
const 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; | ||
} | ||
const url = new URL(location.href); | ||
url.searchParams.set('gl-window', storageKey); | ||
return url.toString(); | ||
} | ||
@@ -269,0 +263,0 @@ /** |
@@ -7,2 +7,3 @@ import { ComponentItemConfig } from '../config/config'; | ||
import { DragProxy } from './drag-proxy'; | ||
import { ResolvedRowOrColumnItemConfig } from "../config/resolved-config"; | ||
/** | ||
@@ -35,7 +36,5 @@ * Allows for any DOM item to create a component on drag | ||
this._dragListener = null; | ||
// Need to review dummyGroundContainer | ||
// Should this part of a fragment or template? | ||
// Does this need to be regenerated with each drag operation? | ||
this._dummyGroundContainer = document.createElement('div'); | ||
this._dummyGroundContentItem = new GroundItem(this._layoutManager, this._layoutManager.layoutConfig.root, this._dummyGroundContainer); | ||
const dummyRootItemConfig = ResolvedRowOrColumnItemConfig.createDefault('row'); | ||
this._dummyGroundContentItem = new GroundItem(this._layoutManager, dummyRootItemConfig, this._dummyGroundContainer); | ||
this.createDragListener(); | ||
@@ -42,0 +41,0 @@ } |
@@ -84,3 +84,5 @@ import { UnexpectedUndefinedError } from '../errors/internal-error'; | ||
this._tabControlOffset = this._layoutManager.layoutConfig.settings.tabControlOffset; | ||
this._tabDropdownButton = new HeaderButton(this, this._tabDropdownLabel, "lm_tabdropdown" /* TabDropdown */, () => this._tabsContainer.showAdditionalTabsDropdown()); | ||
if (this._tabDropdownEnabled) { | ||
this._tabDropdownButton = new HeaderButton(this, this._tabDropdownLabel, "lm_tabdropdown" /* TabDropdown */, () => this._tabsContainer.showAdditionalTabsDropdown()); | ||
} | ||
if (this._popoutEnabled) { | ||
@@ -105,7 +107,4 @@ this._popoutButton = new HeaderButton(this, this._popoutLabel, "lm_popout" /* Popout */, () => this.handleButtonPopoutEvent()); | ||
// private _activeComponentItem: ComponentItem | null = null; // only used to identify active tab | ||
/** @internal */ | ||
get show() { return this._show; } | ||
/** @internal */ | ||
get side() { return this._side; } | ||
/** @internal */ | ||
get leftRightSided() { return this._leftRightSided; } | ||
@@ -116,20 +115,5 @@ get layoutManager() { return this._layoutManager; } | ||
get lastVisibleTabIndex() { return this._tabsContainer.lastVisibleTabIndex; } | ||
/** | ||
* @deprecated use {@link (Stack:class).getActiveComponentItem} */ | ||
get activeContentItem() { | ||
const activeComponentItem = this._getActiveComponentItemEvent(); | ||
if (activeComponentItem === undefined) { | ||
return null; | ||
} | ||
else { | ||
return activeComponentItem; | ||
} | ||
} | ||
get element() { return this._element; } | ||
/** @deprecated use {@link (Header:class).tabsContainerElement} */ | ||
get tabsContainer() { return this._tabsContainer.element; } | ||
get tabsContainerElement() { return this._tabsContainer.element; } | ||
get controlsContainerElement() { return this._controlsContainerElement; } | ||
/** @deprecated use {@link (Header:class).controlsContainerElement} */ | ||
get controlsContainer() { return this._controlsContainerElement; } | ||
/** | ||
@@ -312,3 +296,5 @@ * Destroys the entire header | ||
processTabDropdownActiveChanged() { | ||
setElementDisplayVisibility(this._tabDropdownButton.element, this._tabsContainer.dropdownActive); | ||
if (this._tabDropdownButton !== undefined) { | ||
setElementDisplayVisibility(this._tabDropdownButton.element, this._tabsContainer.dropdownActive); | ||
} | ||
} | ||
@@ -315,0 +301,0 @@ /** @internal */ |
@@ -9,4 +9,5 @@ import { ResolvedComponentItemConfig } from './config/resolved-config'; | ||
export class GoldenLayout extends VirtualLayout { | ||
constructor() { | ||
super(...arguments); | ||
/** @internal */ | ||
constructor(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler) { | ||
super(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler, true); | ||
/** @internal */ | ||
@@ -22,2 +23,6 @@ this._componentTypesMap = new Map(); | ||
this._containerVirtualZIndexChangeRequiredEventListener = (container, logicalZIndex, defaultZIndex) => this.handleContainerVirtualZIndexChangeRequiredEvent(container, logicalZIndex, defaultZIndex); | ||
// we told VirtualLayout to not call init() (skipInit set to true) so that Golden Layout can initialise its properties before init is called | ||
if (!this.deprecatedConstructor) { | ||
this.init(); | ||
} | ||
} | ||
@@ -24,0 +29,0 @@ /** |
@@ -44,2 +44,3 @@ import { AssertError, UnexpectedNullError } from '../errors/internal-error'; | ||
get id() { return this._id; } | ||
set id(value) { this._id = value; } | ||
/** @internal */ | ||
@@ -46,0 +47,0 @@ get popInParentIds() { return this._popInParentIds; } |
@@ -78,2 +78,3 @@ import { ItemConfig } from '../config/config'; | ||
get childElementContainer() { return this._childElementContainer; } | ||
get header() { return this._header; } | ||
get headerShow() { return this._header.show; } | ||
@@ -80,0 +81,0 @@ get headerSide() { return this._header.side; } |
/** @internal */ | ||
export function getQueryStringParam(key) { | ||
const matches = location.search.match(new RegExp(key + '=([^&]*)')); | ||
return matches ? matches[1] : null; | ||
} | ||
/** @internal */ | ||
export function numberToPixels(value) { | ||
@@ -8,0 +3,0 @@ return value.toString(10) + 'px'; |
@@ -7,16 +7,15 @@ import { LayoutConfig } from './config/config'; | ||
import { i18nStrings } from './utils/i18n-strings'; | ||
import { getQueryStringParam } from './utils/utils'; | ||
/** @public */ | ||
export class VirtualLayout extends LayoutManager { | ||
/** @internal */ | ||
constructor(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler) { | ||
constructor(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler, skipInit) { | ||
super(VirtualLayout.createLayoutManagerConstructorParameters(configOrOptionalContainer, containerOrBindComponentEventHandler)); | ||
/** @internal */ | ||
this._subWindowsCreated = false; | ||
/** @internal */ | ||
this._creationTimeoutPassed = false; | ||
// More work needed to get popouts working with virtual. | ||
/** @internal @deprecated use while constructor is not determinate */ | ||
this._bindComponentEventHanlderPassedInConstructor = false; // remove when constructor is determinate | ||
/** @internal @deprecated use while constructor is not determinate */ | ||
this._creationTimeoutPassed = false; // remove when constructor is determinate | ||
if (containerOrBindComponentEventHandler !== undefined) { | ||
if (typeof containerOrBindComponentEventHandler === 'function') { | ||
this.bindComponentEvent = containerOrBindComponentEventHandler; | ||
this._bindComponentEventHanlderPassedInConstructor = true; | ||
if (unbindComponentEventHandler !== undefined) { | ||
@@ -27,7 +26,23 @@ this.unbindComponentEvent = unbindComponentEventHandler; | ||
} | ||
if (this.isSubWindow) { | ||
document.body.style.visibility = 'hidden'; | ||
if (!this._bindComponentEventHanlderPassedInConstructor) { | ||
// backward compatibility | ||
if (this.isSubWindow) { | ||
// document.body.style.visibility = 'hidden'; | ||
// Set up layoutConfig since constructor is not determinate and may exit early. Other functions may need | ||
// this.layoutConfig. this.layoutConfig is again calculated in the same way when init() completes. | ||
// Remove this when constructor is determinate. | ||
if (this._constructorOrSubWindowLayoutConfig === undefined) { | ||
throw new UnexpectedUndefinedError('VLC98823'); | ||
} | ||
else { | ||
const resolvedLayoutConfig = LayoutConfig.resolve(this._constructorOrSubWindowLayoutConfig); | ||
// remove root from layoutConfig | ||
this.layoutConfig = Object.assign(Object.assign({}, resolvedLayoutConfig), { root: undefined }); | ||
} | ||
} | ||
} | ||
if (this.layoutConfig.root === undefined || this.isSubWindow) { | ||
this.init(); | ||
if (skipInit !== true) { | ||
if (!this.deprecatedConstructor) { | ||
this.init(); | ||
} | ||
} | ||
@@ -53,14 +68,5 @@ } | ||
/** | ||
* Create the popout windows straight away. If popouts are blocked | ||
* an error is thrown on the same 'thread' rather than a timeout and can | ||
* be caught. This also prevents any further initilisation from taking place. | ||
*/ | ||
if (this._subWindowsCreated === false) { | ||
this.createSubWindows(); | ||
this._subWindowsCreated = true; | ||
} | ||
/** | ||
* If the document isn't ready yet, wait for it. | ||
*/ | ||
if (document.readyState === 'loading' || document.body === null) { | ||
if (!this._bindComponentEventHanlderPassedInConstructor && (document.readyState === 'loading' || document.body === null)) { | ||
document.addEventListener('DOMContentLoaded', () => this.init(), { passive: true }); | ||
@@ -74,3 +80,3 @@ return; | ||
*/ | ||
if (this.isSubWindow === true && this._creationTimeoutPassed === false) { | ||
if (!this._bindComponentEventHanlderPassedInConstructor && this.isSubWindow === true && !this._creationTimeoutPassed) { | ||
setTimeout(() => this.init(), 7); | ||
@@ -81,6 +87,68 @@ this._creationTimeoutPassed = true; | ||
if (this.isSubWindow === true) { | ||
this.adjustToWindowMode(); | ||
if (!this._bindComponentEventHanlderPassedInConstructor) { | ||
this.clearHtmlAndAdjustStylesForSubWindow(); | ||
} | ||
// Expose this instance on the window object to allow the opening window to interact with it | ||
window.__glInstance = this; | ||
} | ||
super.init(); | ||
} | ||
/** | ||
* Clears existing HTML and adjusts style to make window suitable to be a popout sub window | ||
* Curently is automatically called when window is a subWindow and bindComponentEvent is not passed in the constructor | ||
* If bindComponentEvent is not passed in the constructor, the application must either call this function explicitly or | ||
* (preferably) make the window suitable as a subwindow. | ||
* In the future, it is planned that this function is NOT automatically called in any circumstances. Applications will | ||
* need to determine whether a window is a Golden Layout popout window and either call this function explicitly or | ||
* hide HTML not relevant to the popout. | ||
* See apitest for an example of how HTML is hidden when popout windows are displayed | ||
*/ | ||
clearHtmlAndAdjustStylesForSubWindow() { | ||
const headElement = document.head; | ||
const appendNodeLists = new Array(4); | ||
appendNodeLists[0] = document.querySelectorAll('body link'); | ||
appendNodeLists[1] = document.querySelectorAll('body style'); | ||
appendNodeLists[2] = document.querySelectorAll('template'); | ||
appendNodeLists[3] = document.querySelectorAll('.gl_keep'); | ||
for (let listIdx = 0; listIdx < appendNodeLists.length; listIdx++) { | ||
const appendNodeList = appendNodeLists[listIdx]; | ||
for (let nodeIdx = 0; nodeIdx < appendNodeList.length; nodeIdx++) { | ||
const node = appendNodeList[nodeIdx]; | ||
headElement.appendChild(node); | ||
} | ||
} | ||
const bodyElement = document.body; | ||
bodyElement.innerHTML = ''; | ||
bodyElement.style.visibility = 'visible'; | ||
this.checkAddDefaultPopinButton(); | ||
/* | ||
* This seems a bit pointless, but actually causes a reflow/re-evaluation getting around | ||
* slickgrid's "Cannot find stylesheet." bug in chrome | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const x = document.body.offsetHeight; | ||
} | ||
/** | ||
* Will add button if not popinOnClose specified in settings | ||
* @returns true if added otherwise false | ||
*/ | ||
checkAddDefaultPopinButton() { | ||
if (this.layoutConfig.settings.popInOnClose) { | ||
return false; | ||
} | ||
else { | ||
const popInButtonElement = document.createElement('div'); | ||
popInButtonElement.classList.add("lm_popin" /* Popin */); | ||
popInButtonElement.setAttribute('title', this.layoutConfig.header.dock); | ||
const iconElement = document.createElement('div'); | ||
iconElement.classList.add("lm_icon" /* Icon */); | ||
const bgElement = document.createElement('div'); | ||
bgElement.classList.add("lm_bg" /* Bg */); | ||
popInButtonElement.appendChild(iconElement); | ||
popInButtonElement.appendChild(bgElement); | ||
popInButtonElement.addEventListener('click', () => this.emit('popIn')); | ||
document.body.appendChild(popInButtonElement); | ||
return true; | ||
} | ||
} | ||
/** @internal */ | ||
@@ -126,61 +194,2 @@ bindComponent(container, itemConfig) { | ||
} | ||
/** | ||
* Creates Subwindows (if there are any). Throws an error | ||
* if popouts are blocked. | ||
* @internal | ||
*/ | ||
createSubWindows() { | ||
for (let i = 0; i < this.layoutConfig.openPopouts.length; i++) { | ||
const popoutConfig = this.layoutConfig.openPopouts[i]; | ||
this.createPopoutFromPopoutLayoutConfig(popoutConfig); | ||
} | ||
} | ||
/** | ||
* This is executed when GoldenLayout detects that it is run | ||
* within a previously opened popout window. | ||
* @internal | ||
*/ | ||
adjustToWindowMode() { | ||
const headElement = document.head; | ||
const appendNodeLists = new Array(4); | ||
appendNodeLists[0] = document.querySelectorAll('body link'); | ||
appendNodeLists[1] = document.querySelectorAll('body style'); | ||
appendNodeLists[2] = document.querySelectorAll('template'); | ||
appendNodeLists[3] = document.querySelectorAll('.gl_keep'); | ||
for (let listIdx = 0; listIdx < appendNodeLists.length; listIdx++) { | ||
const appendNodeList = appendNodeLists[listIdx]; | ||
for (let nodeIdx = 0; nodeIdx < appendNodeList.length; nodeIdx++) { | ||
const node = appendNodeList[nodeIdx]; | ||
headElement.appendChild(node); | ||
} | ||
} | ||
const bodyElement = document.body; | ||
bodyElement.innerHTML = ''; | ||
bodyElement.style.visibility = 'visible'; | ||
if (!this.layoutConfig.settings.popInOnClose) { | ||
const popInButtonElement = document.createElement('div'); | ||
popInButtonElement.classList.add("lm_popin" /* Popin */); | ||
popInButtonElement.setAttribute('title', this.layoutConfig.header.dock); | ||
const iconElement = document.createElement('div'); | ||
iconElement.classList.add("lm_icon" /* Icon */); | ||
const bgElement = document.createElement('div'); | ||
bgElement.classList.add("lm_bg" /* Bg */); | ||
popInButtonElement.appendChild(iconElement); | ||
popInButtonElement.appendChild(bgElement); | ||
popInButtonElement.addEventListener('click', () => this.emit('popIn')); | ||
bodyElement.appendChild(popInButtonElement); | ||
} | ||
/* | ||
* This seems a bit pointless, but actually causes a reflow/re-evaluation getting around | ||
* slickgrid's "Cannot find stylesheet." bug in chrome | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const x = document.body.offsetHeight; | ||
/* | ||
* Expose this instance on the window object | ||
* to allow the opening window to interact with | ||
* it | ||
*/ | ||
window.__glInstance = this; | ||
} | ||
} | ||
@@ -196,3 +205,3 @@ /** @public */ | ||
function createLayoutManagerConstructorParameters(configOrOptionalContainer, containerOrBindComponentEventHandler) { | ||
const windowConfigKey = subWindowChecked ? null : getQueryStringParam('gl-window'); | ||
const windowConfigKey = subWindowChecked ? null : new URL(document.location.href).searchParams.get('gl-window'); | ||
subWindowChecked = true; | ||
@@ -209,3 +218,7 @@ const isSubWindow = windowConfigKey !== null; | ||
const minifiedWindowConfig = JSON.parse(windowConfigStr); | ||
config = ResolvedLayoutConfig.unminifyConfig(minifiedWindowConfig); | ||
const resolvedConfig = ResolvedLayoutConfig.unminifyConfig(minifiedWindowConfig); | ||
config = LayoutConfig.fromResolved(resolvedConfig); | ||
if (configOrOptionalContainer instanceof HTMLElement) { | ||
containerElement = configOrOptionalContainer; | ||
} | ||
} | ||
@@ -222,8 +235,4 @@ else { | ||
else { | ||
if (LayoutConfig.isResolved(configOrOptionalContainer)) { | ||
config = configOrOptionalContainer; | ||
} | ||
else { | ||
config = LayoutConfig.resolve(configOrOptionalContainer); | ||
} | ||
// backwards compatibility | ||
config = configOrOptionalContainer; | ||
} | ||
@@ -238,3 +247,3 @@ } | ||
return { | ||
layoutConfig: config, | ||
constructorOrSubWindowLayoutConfig: config, | ||
isSubWindow, | ||
@@ -241,0 +250,0 @@ containerElement, |
{ | ||
"name": "golden-layout", | ||
"version": "2.3.0", | ||
"version": "2.4.0", | ||
"description": "A multi-screen javascript Layout manager", | ||
@@ -13,2 +13,6 @@ "keywords": [ | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/golden-layout/golden-layout.git" | ||
}, | ||
"homepage": "https://github.com/golden-layout/golden-layout", | ||
@@ -15,0 +19,0 @@ "bugs": { |
704
README.md
@@ -5,22 +5,11 @@ # Golden Layout | ||
<!-- [![NPM version](https://badge.fury.io/js/golden-layout.svg)](http://badge.fury.io/js/golden-layout) [![Build Status](https://travis-ci.org/deepstreamIO/golden-layout.svg?branch=master)](https://travis-ci.org/deepstreamIO/golden-layout) --> | ||
Golden Layout is a Javascript layout manager which enables you to layout components in a web page and re-arrange them with drag and drop. Its features include: | ||
<!-- ![Screenshot](https://cloud.githubusercontent.com/assets/512416/4584449/e6c154a0-4ffa-11e4-81a8-a7e5f8689dc5.PNG) --> | ||
## Table of Contents | ||
- [Features](#features) | ||
- [Installation](#installation--usage) | ||
- [Examples](#code-examples) | ||
- [Notes](#notes) | ||
- [Version 2](#version-2) | ||
- [Migration Guide](#migration-to-v2) | ||
## Features | ||
* Native popup windows | ||
* Full touch support | ||
* Touch support | ||
* Support for application frameworks such as Angular and Vue | ||
* Virtual components | ||
* Comprehensive API | ||
* Powerful persistence | ||
* Load and save layouts | ||
* Focus components | ||
* Completely themeable | ||
@@ -30,683 +19,8 @@ * Works in modern browsers (Firefox, Chrome) | ||
## Installation / Usage | ||
### Library | ||
Golden Layout is shipped via NPM. Use the following commands to install it into an application package:\ | ||
## Installation | ||
The library can be installed into an application package with the npm command:\ | ||
`npm i golden-layout` | ||
### Source | ||
The source can be installed by cloning the repository at:\ | ||
`https://github.com/golden-layout/golden-layout` | ||
## More information | ||
To build the distribution locally, open a shell at the golden-layout directory/folder and run the following commands: | ||
1. `npm install` or `npm ci` (recommended) to install required dependencies | ||
1. `npm run build` to generate the distribution (`dist` subfolder). This script will: | ||
* delete the existing `lib` and `dist` folders | ||
* compile the TypeScript code | ||
* generate the rolled up TypeScript definition files (`index.d.ts` and `golden-layout-untrimmed.d.ts`) | ||
* generate source map | ||
* copy the style files to the `dist` folder | ||
Note that the `lib` subfolder only holds the TypeScript declaration files generated by the compiler. Generally this subfolder can be ignored. It is used during the build process to generate the rolled up TypeScript definition files. | ||
### Build and run demo/test app | ||
After installing the source and building the distribution, you can build and start the `apitest` (demo) app to view the library in action. Use the following commands: | ||
* `npm run apitest:build` to just build it | ||
* `npm run apitest:serve` to both build and start the development server.\ | ||
You can then view it in your browser using the following link:\ | ||
http://localhost:3000/ | ||
### Building single-file bundles | ||
We provide different types of single file bundles for easier consumption without toolchain in-place. To do this, run `npm run build:bundles`, afterwards find your bundled files in `dist/bundle/`. | ||
Bundles are not built by default and are not included in the NPM package, we recommend everyone to consume the library through NPM and webpack. | ||
### Debugging Golden Layout library | ||
The `apitest` app can be used to debug the Golden Layout library. Its `webpack` configuration will import the Golden Layout library source map, allowing debuggers to step through the library source code and place break points. | ||
If you wish to test the library with other applications, you can link to the Golden Layout repository without having to install it into the application from NPM. This is done with the `npm link` command. Use the following steps: | ||
1. Run the `npm link` from a shell in the golden-layout source repository top level folder. | ||
1. Run `npm link golden-layout` from a shell in your application's top level folder. | ||
Your application will then use the distribution in the Golden Layout repository `dist` subfolder. If you wish to make changes to the Golden Layout library, you will need to run the `build:api` to regenerate the `dist` folder. | ||
Run `npm install` to remove the npm link. | ||
## Code Examples | ||
### Angular | ||
An example Angular application using Golden Layout is available. The source can be installed by cloning the repository:\ | ||
[https://github.com/golden-layout/golden-layout-ng-app](https://github.com/golden-layout/golden-layout-ng-app) | ||
After installing the source, the app can be built and started with the standard build and start scripts. | ||
### Vue | ||
The following snippets of code demonstrate how Golden Layout can be used in Vue. | ||
These snippets use [**embedding via events**](#embedding-via-events) binding. It may be preferable to use [**virtual via events**](#virtual-via-events) binding (ie Virtual Components). A Pull Request from a Vue user demonstrating using Vue with virtual components would be appreciated. | ||
#### Composable Hook | ||
```ts | ||
import { GoldenLayout, LayoutConfig } from 'golden-layout'; | ||
import { onMounted, ref, shallowRef } from 'vue'; | ||
export const isClient = typeof window !== 'undefined'; | ||
export const isDocumentReady = () => isClient && document.readyState === 'complete' && document.body != null; | ||
export function useDocumentReady(func: () => void) { | ||
onMounted(() => { | ||
console.log(isDocumentReady()); | ||
if (isDocumentReady()) func(); | ||
else | ||
document.addEventListener('readystatechange', () => isDocumentReady() && func(), { | ||
passive: true, | ||
}); | ||
}); | ||
} | ||
export function useGoldenLayout( | ||
createComponent: (type: string, container: HTMLElement) => ComponentContainer.Component, | ||
destroyComponent: (container: HTMLElement) => void, | ||
config?: LayoutConfig | ||
) { | ||
const element = shallowRef<HTMLElement | null>(null); | ||
const layout = shallowRef<GoldenLayout | null>(null); | ||
const initialized = ref(false); | ||
useDocumentReady(() => { | ||
if (element.value == null) throw new Error('Element must be set.'); | ||
const goldenLayout = new GoldenLayout(element.value); | ||
goldenLayout.bindComponentEvent = (container, itemConfig) => { | ||
const { componentType } = itemConfig; | ||
if (typeof componentType !== 'string') throw new Error('Invalid component type.'); | ||
const component = createComponent(componentType, container.element); | ||
return { | ||
component, | ||
virtual: false, | ||
} | ||
} | ||
goldenLayout.unbindComponentEvent = container => { | ||
destroyComponent(container.element); | ||
} | ||
if (config != null) goldenLayout.loadLayout(config); | ||
// https://github.com/microsoft/TypeScript/issues/34933 | ||
layout.value = goldenLayout as any; | ||
initialized.value = true; | ||
}); | ||
return { element, initialized, layout }; | ||
} | ||
``` | ||
#### Usage | ||
```vue | ||
<template> | ||
<div ref="element" style="width: 100%; height: 75vh"> | ||
<teleport | ||
v-for="{ id, type, element } in componentInstances" | ||
:key="id" | ||
:to="element" | ||
> | ||
<component :is="type"></component> | ||
</teleport> | ||
</div> | ||
</template> | ||
<script lang="ts"> | ||
import { useGoldenLayout } from "@/use-golden-layout"; | ||
import { defineComponent, h, shallowRef } from "vue"; | ||
import "golden-layout/dist/css/goldenlayout-base.css"; | ||
import "golden-layout/dist/css/themes/goldenlayout-dark-theme.css"; | ||
const Test = defineComponent({ render: () => h('span', 'It works!') }); | ||
const components = { Test, /* other components */ }; | ||
export default defineComponent({ | ||
components, | ||
setup() { | ||
interface ComponentInstance { | ||
id: number; | ||
type: string; | ||
element: HTMLElement; | ||
} | ||
let instanceId = 0; | ||
const componentTypes = new Set(Object.keys(components)); | ||
const componentInstances = shallowRef<ComponentInstance[]>([]); | ||
const createComponent = (type: string, element: HTMLElement) => { | ||
if (!componentTypes.has(type)) { | ||
throw new Error(`Component not found: '${type}'`); | ||
} | ||
++instanceId; | ||
componentInstances.value = componentInstances.value.concat({ | ||
id: instanceId, | ||
type, | ||
element, | ||
}); | ||
}; | ||
const destroyComponent = (toBeRemoved: HTMLElement) => { | ||
componentInstances.value = componentInstances.value.filter( | ||
({ element }) => element !== toBeRemoved | ||
); | ||
}; | ||
const { element } = useGoldenLayout(createComponent, destroyComponent, { | ||
root: { | ||
type: "column", | ||
content: [ | ||
{ | ||
type: "component", | ||
componentType: "Test", | ||
}, | ||
{ | ||
type: "component", | ||
componentType: "Test", | ||
}, | ||
], | ||
}, | ||
}); | ||
return { element, componentInstances }; | ||
}, | ||
}); | ||
</script> | ||
``` | ||
### Other Frameworks | ||
Other frameworks can use use the `bindComponentEvent` and `unbindComponentEvent` events to manage components bindings with Golden Layout. The `VirtualLayout.bindComponentEvent` event will be fired whenever a component is needed. The `VirtualLayout.unbindComponentEvent` event will be fired when a component is no longer needed and gives the application a chance to tear-down the component. | ||
These events can be set up in an application's start up code as shown in the example code below. | ||
```js | ||
this._goldenLayout = new GoldenLayout(goldenLayoutHostElement); | ||
this._goldenLayout.bindComponentEvent = (container, itemConfig) => { | ||
const component = this.createFrameworkComponent(itemConfig); | ||
// component.rootHtmlElement is the top most HTML element in the component | ||
container.element.appendChild(component.rootHtmlElement); | ||
this._containerMap.set(container, component); | ||
return { | ||
component, | ||
virtual: false, | ||
} | ||
} | ||
this._goldenLayout.unbindComponentEvent = (container, component) => { | ||
const component = this._containerMap.get(container); | ||
container.element.removeChild(component.rootHtmlElement); | ||
this._containerMap.delete(container); | ||
this.disposeFrameworkComponent(component); | ||
} | ||
``` | ||
Once the `VirtualLayout.bindComponentEvent` and `VirtualLayout.unbindComponentEvent` events have been set up, functions that create components can be used. For example: | ||
* `LayoutManager.loadLayout()` | ||
* `LayoutManager.addComponent()` | ||
The above example uses 'embedding via events' binding. It is also possible to use these events with 'virtual via events' binding. For more information on binding, see the [Binding Components](#binding-components) section below. | ||
## Notes | ||
### Using Popouts | ||
Popouts are supported, although the scope is more limited than in the original v1. Popouts are enabled by default for all content items. Popouts are disabled by either setting `{ popout: false }` in the `header` configuration or when a component is not closable. Also, as a popout user, make sure to register all component types before initializing the golden-layout instance in your child windows. | ||
Popout examples are available in the `standard` and `tabDropdown` layouts within the apitest application. | ||
EventHub can be used to broadcast messages and events to all windows. The LayoutManager.eventHub.emitUserBroadcast() function is used to broadcast messages. Messages can be received by listening to “userBroadcast” events. For example: | ||
```ts | ||
layoutManager.eventHub.on('userBroadcast', (...ev: EventEmitter.UnknownParams) => { | ||
// respond to user broadcast event | ||
}); | ||
``` | ||
See event-component.ts in apitest for a complete example of broadcasting user messages. | ||
#### Limitations | ||
- The EventHub is restricted to `userBroadcast` events, other event types will not be broadcasted between windows. | ||
- This means the you have to take care of propagating state changes between windows yourself. | ||
### Binding Components | ||
Golden Layout binds to components and then controls their position, size and visibility (positioning) so that they fit within a layout. There are 4 ways Golden Layout can bind to a component: | ||
1. **Embedding via Registration** (classic)\ | ||
A component's constructor/factory is registered with Golden Layout. Golden Layout will instantiate an instance when required. The constructor/factory will add the component's root HTML element within Golden Layout's own DOM hierarchy sub-tree. This is the classic Golden Layout binding method. With this binding method an ancestor of the component's root HTML element could be reparented if the layout changes. | ||
1. **Embedding via Events**\ | ||
Components are obtained on demand by events. An event handler will construct or otherwise fetch the component and return it. The event handler will also add the component's root HTML element within Golden Layout's own DOM hierarchy sub-tree. This is the binding method introduced in version 2. | ||
1. **Virtual via Registration**\ | ||
A component's constructor/factory is registered with Golden Layout. Golden Layout will instantiate an instance when required. The component will use the same positioning as virtual components however Golden Layout will handle all the events internally. | ||
1. **Virtual via Events** (Virtual Components)\ | ||
With virtual components, Golden Layout never interacts directly with components. The application controls the construction/allocation, destruction/deallocation and positioning of components. Golden Layout will advise the application when components are needed and no longer needed via events. It will also advise about components' positioning via events. This allows an application to control the placement of components in the DOM hierarchy and components' root HTML element's ancestors are not reparented when the layout is changed. | ||
#### Embedding via Registration | ||
Registering a component and specifying static positioning is the classic GoldenLayout approach to binding components. The components are registered with GoldenLayout and specify a constructor or callback function used to create a component whenever a new instance is needed in the layout. When the constructor or callback is invoked, it is passed a container object which includes a HTML element. The constructor or callback will create the object and make its top level HTML element a child of the container's HTML element. The component is then part of the Golden Layout's DOM hierarchy. Whenever the layout is re-arranged, the GoldenLayout DOM is adjusted to reflect the new layout hierarchy. Effectively this involves the ancestors of components' root HTML elements being reparented when a layout is changed. | ||
The following functions can be used to register components. | ||
* `GoldenLayout.registerComponent()` | ||
* `GoldenLayout.registerComponentConstructor()` | ||
* `GoldenLayout.registerComponentFactoryFunction()` | ||
* `GoldenLayout.registerComponentFunction()` | ||
* `GoldenLayout.registerGetComponentConstructorCallback()` | ||
#### Embedding via Events | ||
To give applications more control over the allocation of components, you can bind components with events instead of registration. If a handler is assigned to the event `VirtualLayout.bindComponentEvent` it will be fired whenever a new component is needed. The handler should: | ||
* create or fetch the component, | ||
* make sure its top level HTML elements are made children of `container.element`, | ||
* return the component inside a `BindableComponent` interface with `virtual: false`. | ||
When a component is removed from Golden Layout it will be necessary to remove the component's top level HTML elements as children of `container.element`. Other component 'tear-down' actions may also be required. These actions can be carried out in either the `VirtualLayout.unbindComponentEvent` event or the component container's `beforeComponentRelease` event (or both). Both these events will be fired (if handlers are assigned) when a component is no longer needed in Golden Layout. | ||
#### Virtual via Events | ||
With virtual components, Golden Layout knows nothing about components and does not include the component's HTML elements in its own DOM hierarchy. Instead, whenever a component needs its position, size or visibility changed, Golden Layout will fire events which allow the application to change a component's position, size or visibility. This is analogous to virtual grids where strings and other content to be displayed in a grid, are not included within the grid. Instead the grid fires events whenever it needs to display content. The application will return the required content. | ||
Virtual Components has the following advantages: | ||
* Components and their ancestors are not reparented when a layout is changed. This avoids breaking iframe, sockets, etc. | ||
* It is no longer necessary to extract the top level HTML element from a component. | ||
* Applications using frameworks with their own component hierarchy, such as Angular and Vue, no longer have to break their component hierarchy to insert Golden Layout. The framework's methodology for handling parent/child relationships can be maintained even with the components which Golden Layout is positioning. (Teleporting component's HTML elements is no longer necessary) | ||
* Applications typically bind a component's top level HTML element to the Golden Layout root element. Debugging becomes easier as the DOM hierarchy relevant to your application is a lot shallower. | ||
With Virtual Components the following events need to be handled: | ||
* `VirtualLayout.bindComponentEvent: (container, itemConfig) => ComponentContainer.BindableComponent`\ | ||
Fired whenever a GoldenLayout wants to bind to a new component. The handler is passed the container and the item's resolved config. Typically, the handler would: | ||
* create or fetch the component using `itemConfig`, | ||
* get the the component's top level HTML component, | ||
* ensure this element has `absolute` position, | ||
* make the element a child of Golden Layout's root HTML element, | ||
* store the component in a map using `container` as the key, | ||
* add handlers to the container's `virtualRectingRequiredEvent` and `virtualVisibilityChangeRequiredEvent` events, | ||
* return the component in an `BindableComponent` interface with `virtual: true`. | ||
Example: | ||
~~~ | ||
private handleBindComponentEvent(container: ComponentContainer, itemConfig: ResolvedComponentItemConfig) { | ||
// Use ResolvedComponentItemConfig.resolveComponentTypeNamecan to resolve component types to a unique name | ||
const componentTypeName = ResolvedComponentItemConfig.resolveComponentTypeName(itemConfig); | ||
if (componentTypeName === undefined) { | ||
throw new Error('handleBindComponentEvent: Undefined componentTypeName'); | ||
} | ||
const component = this.createVirtualComponent(container, componentTypeName, itemConfig.componentState); | ||
const componentRootElement = component.rootHtmlElement; | ||
this._layoutElement.appendChild(componentRootElement); | ||
this._boundComponentMap.set(container, component); | ||
container.virtualRectingRequiredEvent = (container, width, height) => this.handleContainerVirtualRectingRequiredEvent(container, width, height); | ||
container.virtualVisibilityChangeRequiredEvent = (container, visible) => this.handleContainerVisibilityChangeRequiredEvent(container, visible); | ||
return { | ||
component, | ||
virtual: true, | ||
}; | ||
} | ||
~~~ | ||
* `VirtualLayout.unbindComponentEvent: (container) => void`\ | ||
Fired when a component is removed from Golden Layout. The handler is passed the container. Typically, the handler would: | ||
* find the component in the map using `container` as the key, | ||
* remove it as a child from Golden Layout's root HTML element, | ||
* remove it from the map. | ||
Example: | ||
~~~ | ||
private handleUnbindComponentEvent(container: ComponentContainer) { | ||
const component = this._boundComponentMap.get(container); | ||
if (component === undefined) { | ||
throw new Error('handleUnbindComponentEvent: Component not found'); | ||
} | ||
const componentRootElement = component.rootHtmlElement; | ||
if (componentRootElement === undefined) { | ||
throw new Error('handleUnbindComponentEvent: Component does not have a root HTML element'); | ||
} | ||
this._layoutElement.removeChild(componentRootElement); | ||
this._boundComponentMap.delete(container); | ||
} | ||
~~~ | ||
* `LayoutManager.beforeVirtualRectingEvent: () => void`\ | ||
This event does not need to be handled. However it can be used to optimise positioning of components. Whenever a layout is changed, it may be that several components need to be repositioned. This event will be fired whenever one or more components need to be positioned as the result of one layout change. Typically it is used to get the position of Golden Layout's root HTML element, using `getBoundingClientRect()`. This can then be cached for use when each component's position needs to be calculated. | ||
Example: | ||
~~~ | ||
private handleBeforeVirtualRectingEvent(count: number) { | ||
this._goldenLayoutBoundingClientRect = this._layoutElement.getBoundingClientRect(); | ||
} | ||
~~~ | ||
* `ComponentContainer.virtualRectingRequiredEvent: (container, width, height) => void;`\ | ||
Fired when a component's position and/or size need to be changed. The handler is passed the container and the component's required width and height. Typically, the handler would: | ||
* find the component in the map using `container` as the key, | ||
* get the Golden Layout's root HTML element's position using `getBoundingClientRect()`, (Alternatively, it can used the position calculated by the handler for the `virtualRectingRequiredEvent` event.) | ||
* get the container's position using `getBoundingClientRect()`, | ||
* calculate the container's position relative to Golden Layout's root HTML element position. | ||
* accordingly, update the following properties in the component's top level HTML element: | ||
* `left` | ||
* `top` | ||
* `width` | ||
* `height` | ||
Example: | ||
~~~ | ||
private handleContainerVirtualRectingRequiredEvent(container: ComponentContainer, width: number, height: number) { | ||
const component = this._boundComponentMap.get(container); | ||
if (component === undefined) { | ||
throw new Error('handleContainerVirtualRectingRequiredEvent: Component not found'); | ||
} | ||
const rootElement = component.rootHtmlElement; | ||
if (rootElement === undefined) { | ||
throw new Error('handleContainerVirtualRectingRequiredEvent: Component does not have a root HTML element'); | ||
} | ||
const containerBoundingClientRect = container.element.getBoundingClientRect(); | ||
const left = containerBoundingClientRect.left - this._goldenLayoutBoundingClientRect.left; | ||
rootElement.style.left = this.numberToPixels(left); | ||
const top = containerBoundingClientRect.top - this._goldenLayoutBoundingClientRect.top; | ||
rootElement.style.top = this.numberToPixels(top); | ||
rootElement.style.width = this.numberToPixels(width); | ||
rootElement.style.height = this.numberToPixels(height); | ||
} | ||
~~~ | ||
* `ComponentContainer.virtualVisibilityChangeRequiredEvent: (container, visible) => void;`\ | ||
Fired when a component's visibility needs to be changed. The handler is passed the container and a boolean specifying visibility. Typically, the handler would: | ||
* find the component in the map using `container` as the key, | ||
* change its visibility using the `display` property in the component's top level HTML element. | ||
Example: | ||
~~~ | ||
private handleContainerVisibilityChangeRequiredEvent(container: ComponentContainer, visible: boolean) { | ||
const component = this._boundComponentMap.get(container); | ||
if (component === undefined) { | ||
throw new Error('handleContainerVisibilityChangeRequiredEvent: Component not found'); | ||
} | ||
const componentRootElement = component.rootHtmlElement; | ||
if (componentRootElement === undefined) { | ||
throw new Error('handleContainerVisibilityChangeRequiredEvent: Component does not have a root HTML element'); | ||
} | ||
if (visible) { | ||
componentRootElement.style.display = ''; | ||
} else { | ||
componentRootElement.style.display = 'none'; | ||
} | ||
} | ||
~~~ | ||
* `ComponentContainer.virtualZIndexChangeRequiredEvent: (container, logicalZIndex, defaultZIndex) => void`\ | ||
Fired when a component's z-index needs to be changed. The handler is passed the container and a logical z-index and the default style z-index. Typically, the handler would: | ||
* find the component in the map using `container` as the key, | ||
* change its z-index to the default style z-index specified in `defaultZIndex`. | ||
Example: | ||
~~~ | ||
private handleContainerVirtualZIndexChangeRequiredEvent(container: ComponentContainer, logicalZIndex: LogicalZIndex, defaultZIndex: string) { | ||
const component = this._boundComponentMap.get(container); | ||
if (component === undefined) { | ||
throw new Error('handleContainerVirtualZIndexChangeRequiredEvent: Component not found'); | ||
} | ||
const componentRootElement = component.rootHtmlElement; | ||
if (componentRootElement === undefined) { | ||
throw new Error('handleContainerVirtualZIndexChangeRequiredEvent: Component does not have a root HTML element'); | ||
} | ||
componentRootElement.style.zIndex = defaultZIndex; | ||
} | ||
~~~ | ||
The apitest application demonstrates how virtual components are implemented. | ||
When using virtual components, think of Golden Layout as more of an engine calculating position rather than actually positioning components. This binding method requires more work to set up than other binding methods. However it offers more flexibility and opens up more design opportunities. For example with virtual components, any HTML element could be the parent for your components (not just the Golden Layout container). You can even have different parents for different components. This allows, for example, some of your components have one parent, and the others a different parent. They could then inherit different CSS or handle event propagation differently. | ||
#### Virtual via Registration | ||
These events give applications a lot of flexibility with positioning components in Golden Layout - but at the expense of more effort of integrating into Golden Layout. It is however, possible to get the same benefits of Virtual Components with just registering a component. In this case, a component will be registered as in classic approach to Golden Layout binding, however, within Golden Layout, the component will be handled like a virtual component. Golden Layout will internally handle the necessary events. | ||
Existing applications using register functions in Golden Layout can easily be updated to use virtual binding by: | ||
1. The register functions have a new parameter `virtual`. By default, this is `false`, specifying the classic binding in Golden Layout. Set this to `true` to specify that components of that type should be implemented internally as virtual components. | ||
1. Components need to have a getter: `rootHtmlElement` which returns the component's root HTML element. Components written in TypeScript should implement the `GoldenLayout.VirtuableComponent` interface. | ||
1. Components' `rootHtmlElement` element need to have its `overflow` CSS property set to hidden. | ||
1. Ensure that the Golden Layout container HTML element is positioned (ie. its position property is not `static`). | ||
With these changes, applications can continue to use Golden Layout as they are now however Golden Layout will internally use virtual component binding. | ||
Please note there will be a couple of minor behaviour changes: | ||
* Golden Layout will ensure a component's root HTML element has position type `absolute`. | ||
* Golden Layout will modify the height and width of the root HTML element. In embedding bindings, Golden Layout modified the height and width of the container element - not the component's root HTML Element. If your application also sets the height or width of a components root HTML element, you will need to modify your design. This can easily be done by giving the current root HTML element a new parent element and making this parent the new root HTML element for the component. Your component logic can continue to use the existing element while Golden Layout uses the new root HTML element. | ||
* Golden Layout will modify the z-index of the component's root HTML element. | ||
Also note that 'virtual via registration' binding is not supported by the `GoldenLayout.registerGetComponentConstructorCallback()` registration function. | ||
#### Multiple binding methods | ||
An application can use multiple methods of binding components for different component types. Whenever a component needs to be bound, Golden Layout will try to bind in the following order: | ||
1. First check if its type has been registered. If so, it will bind using that registration. | ||
1. Check whether there is a `bindComponentEvent` handler. If so, this event will be used to bind it as a virtual component. | ||
1. Check whether there is a `getComponentEvent` handler. If so, this event will be used to bind the component statically within the Golden Layout DOM. This method is deprecated. | ||
1. If none of the above, then an exception will be raised. | ||
If you use both 'Virtual via Events' and 'Embedding via Events', then the `unbindComponentEvent` handler can use the `ComponentContainer.virtual` field to determine which of these binding methods was used for a component. | ||
#### VirtualLayout class | ||
The inheritance hierarchy for the Golden Layout class is: `LayoutManager` -> `VirtualLayout` -> `GoldenLayout`. | ||
The `VirtualLayout` class implements all the Golden Layout functionality except for the register functions. If you only intend to use virtual components using the `bindComponentEvent`, you can create an instance of `VirtualLayout` instead of `GoldenLayout`. | ||
#### Usage Scenarios | ||
* **Quick and easy**\ | ||
Use 'Embedding via Registration'. The classic way of using Golden Layout. | ||
* **Backwards compatibility**\ | ||
If your existing application uses the Golden Layout registration functions, then it will automatically use 'Embedding via Registration' without any changes. | ||
* **Deprecated `getComponentEvent`**\ | ||
To quickly get rid of this deprecation, use 'Embedding via Events'. | ||
* **Easy virtual component bindings** | ||
Use 'Virtual via Registration' to get the advantages of Virtual Component binding with minimal changes to applications. | ||
* **Maximum design flexibility**\ | ||
Use 'Virtual via Events' (Virtual Components). | ||
### Understanding Focus | ||
Components can have focus. This is analagous to HTML Elements having focus. | ||
Only one component in a layout can have focus at any time (or alternatively, no component has focus). Similarly to HTML elements, a component will be focused when you click on its tab. You can programatically give a component focus by calling the `focus()` method on its container. Likewise, you can remove focus from a container by calling `ComponentContainer.blur()`. | ||
Clicking on HTML within a component will not automatically give a Golden Layout component focus. However this can be achieved by listening to the bubbling `click` and/or `focusin` events and calling `ComponentContainer.focus()` in these events' handlers. The `apitest` demonstrates this technique. | ||
A focused component's tab and header HTML elements will contain the class `lm_focused`. This can be used to highlight the focused tab and or header. The `goldenlayout-dark-theme.less` theme that ships with Golden Layout (and is used by `apitest`) will set the background color of a focused tab to a different color from other tabs. If you do NOT want focused tabs to be highlighted, ensure that the `lm_focused` selector is removed from the relevant css/less/scss used by your application. | ||
### Understanding LocationSelectors | ||
LocationSelectors specify the location of a component in terms of a parent and a index. LocationSelectors are useful for specifying where a new ContentItem should be placed. | ||
A `LocationSelector` does not specify the parent directly. Instead it specifies how the parent is to be searched for. It has the type: | ||
``` | ||
export interface LocationSelector { | ||
typeId: LocationSelector.TypeId; | ||
index?: number; | ||
} | ||
``` | ||
`typeId` specifies the algorithm used to search for a parent. | ||
`index` is used by the algorithm to work out the preferred child position under the parent. | ||
Some `LocationSelector.TypeId` will always find a location. Eg: `LocationSelector.TypeId.Root` is guaranteed to find a location. Others may not find a location. Eg: `LocationSelector.TypeId.FirstStack` will not find a location if a layout is empty. | ||
The `LayoutManager.addComponentAtLocation()` and `LayoutManager.newComponentAtLocation()` use an array of LocationSelectors to determine the location at which a new/added component will be installed. These functions will attempt to find a valid location starting with the first element in the LocationSelectors array. When a valid location is found, that location will be used for the new component. If no valid location is found from the LocationSelectors in the array, then the component will not be added. | ||
The `LayoutManager.addComponent()` and `LayoutManager.newComponent()` use a default LocationSelectors array. The last element in this default array is a LocationSelector of type `LocationSelector.TypeId.Root`. So this array is guaranteed to find a location. Accordingly, `LayoutManager.addComponent()` and `LayoutManager.newComponent()` will always succeed in adding a component. | ||
This default LocationSelectors array is available at `LayoutManager.defaultLocationSelectors`. An alternative predefined array is available at `LayoutManager.afterFocusedItemIfPossibleLocationSelectors`. | ||
## Version 2 | ||
This version is a substantial change from the previous (1.5.9) version. The change can be summarised as: | ||
1. The code has been ported to TypeScript | ||
1. The primary focus of maintenance will be on reliability. | ||
Before migrating from version 1, it is **important** to review the following: | ||
#### Dropped Features | ||
As part of the port, the code base was significantly refactored. A number of features have been dropped from the version 1.0 as their implementation was not robust enough to meet the reliability requirements of version 2. The dropped features are: | ||
* **React Support** - The [FlexLayout](https://github.com/caplin/FlexLayout) library has been designed for React components. We recommend developers using React to use this library instead of Golden Layout. | ||
* **Nested Stacks** - While it was possible to create layouts with Nested Stacks in version 1, the implementation was incomplete. Due to the large amount of work that would have been necessary to fix the implementation, it was decided instead to drop this feature. Version 2 explicitly does not allow nested stacks. | ||
* [**Internal and Public API**](#public-and-internal-apis) - All classes, interfaces, functions and properties are marked as either `internal` or `public`. Only `public` APIs are generally available to applications. | ||
* **Legacy Browsers** - The library will now only target modern browsers (see package.json for browserlist configuration) | ||
* **No JQuery** - JQuery is no longer used in Golden Layout (many would consider this as an added feature) | ||
## Migration to v2 | ||
Version 2 has been re-written in TypeScript. A general code cleanup has been carried out as part of this re-write. | ||
Also, some changes have been made to the GoldenLayout API. Where possible, backwards compatibility has been retained,however functions and properties kept for backwards compatibility have been marked as deprecated. It is strongly recommend applications be migrated to the new API. | ||
### Config | ||
Configs are now strongly typed. In addition, GoldenLayout now has "Configs" and "Resolved Configs" | ||
1. Configs\ | ||
Application developers will mainly work with "Configs". A "Config" supports optional properties. If a property is not specified, a default will be used. In addition, "Config" also will handle backwards compatibility. It will migrate deprecated properties to their new values.\ | ||
Config parameters in GoldenLayout API methods will be of type "Config". The one exception is `LayoutConfig.saveLayout()` which returns a "Resolved Config". | ||
1. Resolved Configs\ | ||
Golden-Layout internally uses "Resolved Config"s. Whenever an API function is passed a "Config", GoldenLayout will resolve it to its corresponding "Resolved Config". This resolving process will set default values where an optional value has not been specified. It will also handle backwards compatibility. This allows the GoldenLayout library to always work with fully configured Configs. | ||
For persistence of configs, always save the "Resolved Config" returned by `LayoutManager.saveLayout()`. When reloading a saved Layout, first convert the saved "Resolved Config" to a "Config" by calling `LayoutConfig.fromResolved()`. | ||
Both "Resolved Config" and "Config" have 2 types of interface hierarchies: | ||
1. `ItemConfig`\ | ||
This specifies the config for a content item. | ||
1. `LayoutConfig` (previously the `Config` interface)\ | ||
This specifies the config for a layout. | ||
The (optional) `ItemConfig.id` property now has type `string` (instead of its previous `string | string[]` type). For backwards compatibility, when `ItemConfig.id` is resolved, it will still accept an `id` with of type string array. This will allow handling of legacy saved configs in which `id` contains an array of strings (including possibly the legacy maximise indicator). When such an `id` is resolved, the array is first checked for the legacy maximise indicator and then the first element becomes the `id` string value. The remaining elements are discarded. | ||
The `ComponentItemConfig.componentName` property has now been replaced by property `ComponentItemConfig.componentType`. `componentType` is of type `JsonValue`. While a component type can now be specified by values that can be serialised by JSON, `componentType` must be of type `string` if it is registered with one of the following functions: | ||
1. `GoldenLayout.registerComponent()` (deprecated) | ||
1. `GoldenLayout.registerComponentConstructor()` | ||
1. `GoldenLayout.registerComponentFactoryFunction()` | ||
A `LayoutConfig` has a `root` property which specifies the ItemConfig of root content item of the layout. `root` is not optional and must always be specified. | ||
The `LayoutConfig` `selectionEnabled` property has been removed. Clicking of Stack Headers can now be handled with the new `stackHeaderClick` event (which is always enabled). | ||
`ResolvedLayoutConfig` now has functions to minify and unminify configurations: | ||
1. `minifyConfig()` Replaces `LayoutManager.minifyConfig()` | ||
1. `unminifyConfig()` Replaces `LayoutManager.unminifyConfig()` | ||
For examples of how to create LayoutConfigs, please refer to the `apitest` program in the repository. | ||
Many of the Config properties have been deprecated as they overlapped or were moved to more appropriate locations. Please refer to the `config.ts` source file for more information about these deprecations. | ||
### GoldenLayout class and VirtualLayout class | ||
`GoldenLayout` is now a distinct class which is a descendant of the `VirtualLayout` class, which in turn is a descendant of the `LayoutManager` class. Your application should always create an instance of either `GoldenLayout` or `VirtualLayout`. | ||
The `GoldenLayout` and `VirtualLayout` constructors takes 3 optionals parameters: | ||
1. The HTML element which contains the GoldenLayout instance. If this is not specified, GoldenLayout will be placed under `body`. | ||
2. The `bindComponentEvent` event handler. | ||
3. The `unbindComponentEvent` event handler. | ||
Note that the initial Layout is no longer specified in this constructor. Instead it is loaded with `LayoutManager.loadLayout()` (see below). | ||
The GoldenLayout class now handles component registration. LayoutManager no longer includes any component registration functions. The following changes to registration functions have been made: | ||
1. `registerComponentConstructor()` (new function)\ | ||
Same as previous `registerComponent()` however only used when registering a component constructor. | ||
1. `registerComponentFactoryFunction` (new function)\ | ||
Same as previous `LayoutManager.registerComponent()` however only used when registering a call back function (closure) for creating components. | ||
1. Do not use `registerComponent()`. Use the new `registerComponentConstructor()` or `registerComponentFactoryFunction()` instead. | ||
### LayoutManager changes | ||
1. Do not construct an instance of LayoutManager. Construct an instance of GoldenLayout (see above). | ||
1. Do not call `init()`. Call `LayoutManager.loadLayout()` instead. | ||
1. `loadLayout()` (new function)\ | ||
Will load the new layout specified in its `LayoutConfig` parameter. This can also be subsequently called whenever the GoldenLayout layout is to be replaced. | ||
1. `saveLayout()` (new function)\ | ||
Saves the current layout as a `LayoutConfig`. Replaces the existing `toConfig()` function. | ||
1. Do not uses `minifyConfig()` of `unminifyConfig()` functions. Use the respective functions in `ResolvedLayoutConfig`. | ||
1. Do not call `toConfig()`. Call `LayoutManager.saveLayout()` instead. | ||
1. `setSize()` (new function)\ | ||
Sets the size of the GoldenLayout instance in pixels. Replaces the existing `updateSize()` function. | ||
1. Do not use `updateSize()`. Use the new `LayoutManager.setSize()` instead. | ||
1. `rootItem` (new property) | ||
Specifies the root content item of the layout (not the Ground content item). | ||
1. Do not use `root`. This has been replaced with the internal property `groundItem`. You probably want to use the new `rootItem` instead. | ||
1. `focusComponent()` will focus the specified component item. Only one component item can have focus. If previously, another component item had focus, then it will lose focus (become blurred). `focus` or `blur` events will be emitted as appropriate unless the `suppressEvent` parameter is set to true. | ||
1. `clearComponentFocus()` which removes any existing component item focus. If focus is removed, a `blur` event will be emitted unless the `suppressEvent` parameter is set to true. | ||
### VirtualLayout | ||
1. `getComponentEvent` | ||
Now implemented in the VirtualLayout class but has been deprecated. Use `VirtualLayout.bindComponentEvent` instead. | ||
1. `releaseComponentEvent` (new event)\ | ||
Now implemented in the VirtualLayout class but has been deprecated. Use `VirtualLayout.unbindComponentEvent` instead. | ||
1. See [Binding Components](#binding-components) section for more information about new events related to binding components. | ||
### Content Items | ||
1. `AbstractContentItem` has been renamed to `ContentItem` | ||
1. `config` property has been removed. Use the toConfig() method instead (as recommended in the original GoldenLayout documentation). | ||
1. Some of the previous `config` properties such as `id` and `type` are now available as properties of `ContentItem` or its descendants (where appropriate). | ||
1. `id` now has type `string`. (It used to be `string | string[]`.) | ||
1. `ItemContainer` has been renamed to `ComponentContainer` | ||
1. `Component` has been renamed to `ComponentItem`. "Component" now refers to the external component hosted inside GoldenLayout | ||
1. `Root` has been renamed to `GroundItem` and has been marked as internal only. Applications should never access GroundItem. Note that the layout's root ContentItem is GroundItem's only child. You can access this root ContentItem with `LayoutManager.rootItem`. | ||
1. `Stack.getActiveContentItem()` and `Stack.setActiveContentItem()` have been renamed to respective `Stack.getActiveComponentItem()` and `Stack.setActiveComponentItem()` | ||
1. `ContentItem.select()` and `ContentItem.deselect()` have been removed. Use the new `ComponentItem.focus()` and `ComponentItem.blur()` instead. | ||
1. `ComponentItem.focus()` (new function) will focus the specified ComponentItem. It will also remove focus from another component item which previously had focus. Only one component item can have focus at any time. If layout focus has changed, a `focus` event will be emitted (unless suppressEvent parameter is set to true). | ||
1. `ComponentItem.blur()` (new function) will remove focus from the specified ComponentItem. After this is called, no component item in the layout will have focus. If the component lost focus, a `blur` event will be emitted (unless suppressEvent parameter is set to true). | ||
### ComponentContainer | ||
1. `element` (new property - replaces `getElement()`)\ | ||
Returns HTMLElement which hosts component | ||
1. Do not use `getElement()`. Use the new `element` property instead | ||
1. `initialState` (new getter)\ | ||
Gets the componentState of the `ComponentItemConfig` used to create the contained component. | ||
1. `stateRequestEvent` (new event)\ | ||
If set, `stateRequestEvent` is fired whenever GoldenLayout wants the latest state for a component. Calling `LayoutManager.saveLayout()` will cause this event to be fired (if it is defined). If it is not defined, then the initial state in the ItemConfig or the latest state set in `setState()` will be saved. | ||
1. `beforeComponentRelease` (new EventEmitter event)\ | ||
`beforeComponentRelease` is emitted on the container before a component is released. Components can use this event to dispose of resources. | ||
1. Do not use `getState()` unless you are using the deprecated `setState()`. Use `ComponentContainer.initialState` getter if you have migrated to the new `ComponentContainer.stateRequestEvent`. | ||
1. `setState()` has been marked as deprecated. If possible, use the new `stateRequestEvent` event instead. | ||
1. `replaceComponent()` allows you to replace a component in a container without otherwise affecting the layout. | ||
1. See [Binding Components](#binding-components) section for more information about new events related to binding components. | ||
### Header and Tab | ||
Several properties and functions have been renamed in `header.ts` and `tab.ts`. Please search for "@deprecated" in these files for these changes. | ||
### Events | ||
1. All DOM events are now propagated so that they can be handled by parents or globally. | ||
1. preventDefault() is not called by any event listeners. | ||
1. Bubbling Events are now emitted with the parameter EventEmitter.BubblingEvent (or descendant) | ||
1. New EventEmitter events: | ||
* beforeComponentRelease | ||
* stackHeaderClick - Bubbling event. Fired when stack header is clicked - but not tab. | ||
* stackHeaderTouchStart - Bubbling event. Fired when stack header is touched - but not tab. | ||
* focus - Bubbling event. Fired when a component gets focus. | ||
* blur - Bubbling event. Fired when a component loses focus. | ||
### Other | ||
1. `undefined` is used instead of `null` for new properties, events etc. Some internals have also been switched to use `undefined` instead of `null`. Existing properties using `null` mostly have been left as is however it is possible that some of these internal changes have affected external properties/events/methods. | ||
### Deprecations | ||
For most changes, the existing functions and properties have been left in place but marked as deprecated. It is strongly recommended that applications be reworked not to use these deprecations. Bugs associated with deprecations will be given low priority (or not fixed at all). Also, deprecated aliases, methods and properties may be removed in future releases. | ||
### Public and Internal APIs | ||
All API elements (classes, interfaces, functions etc) have been labelled as either `public` or `internal`. Applications should only use `public` API elements. Internal API elements are subject to change and no consideration will be given to backwards compatibility when these are changed. | ||
The library distribution includes 2 TypeScript declaration (typing) files: | ||
1. `index.d.ts` which contains only public API elements. Applications should use this declaration file to access the library. | ||
1. `golden-layout-untrimmed.d.ts` which contains all (public and internal) API elements. Use this declaration file if you wish to access any API element in the library however please take the above warning into account. | ||
Note that the allocation of API elements to either public or internal has not been finalised. However any element used in either the `apitest` application or the example Angular application will remain labelled as public. | ||
For more information, please refer to the [Golden Layout website](https://golden-layout.github.io/golden-layout) |
@@ -691,3 +691,3 @@ import { ConfigurationError } from '../errors/external-error'; | ||
*/ | ||
tabDropdown?: string; | ||
tabDropdown?: false | string; | ||
} | ||
@@ -694,0 +694,0 @@ |
@@ -480,3 +480,3 @@ import { AssertError, UnreachableCaseError } from '../errors/internal-error'; | ||
readonly close: false | string; | ||
readonly tabDropdown: string; | ||
readonly tabDropdown: false | string; | ||
} | ||
@@ -483,0 +483,0 @@ |
@@ -228,2 +228,20 @@ import { ComponentItemConfig, ItemConfig } from '../config/config'; | ||
this.updateElementPositionPropertyFromBoundComponent(); | ||
if (this._boundComponent.virtual) { | ||
if (this.virtualVisibilityChangeRequiredEvent !== undefined) { | ||
this.virtualVisibilityChangeRequiredEvent(this, this._visible); | ||
} | ||
if (this.virtualRectingRequiredEvent !== undefined) { | ||
this._layoutManager.fireBeforeVirtualRectingEvent(1); | ||
try { | ||
this.virtualRectingRequiredEvent(this, this._width, this._height); | ||
} finally { | ||
this._layoutManager.fireAfterVirtualRectingEvent(); | ||
} | ||
} | ||
if (this.virtualZIndexChangeRequiredEvent !== undefined) { | ||
this.virtualZIndexChangeRequiredEvent(this, LogicalZIndex.base, StyleConstants.defaultComponentBaseZIndex); | ||
} | ||
} | ||
this.emit('stateChanged'); | ||
@@ -230,0 +248,0 @@ } |
@@ -288,12 +288,5 @@ import { ResolvedLayoutConfig, ResolvedPopoutLayoutConfig } from '../config/resolved-config'; | ||
const 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; | ||
} | ||
const url = new URL(location.href); | ||
url.searchParams.set('gl-window', storageKey); | ||
return url.toString(); | ||
} | ||
@@ -300,0 +293,0 @@ |
@@ -9,2 +9,3 @@ import { ComponentItemConfig } from '../config/config'; | ||
import { DragProxy } from './drag-proxy'; | ||
import { ResolvedRowOrColumnItemConfig } from "../config/resolved-config"; | ||
@@ -41,8 +42,6 @@ /** | ||
// Need to review dummyGroundContainer | ||
// Should this part of a fragment or template? | ||
// Does this need to be regenerated with each drag operation? | ||
this._dummyGroundContainer = document.createElement('div'); | ||
this._dummyGroundContentItem = new GroundItem(this._layoutManager, this._layoutManager.layoutConfig.root, this._dummyGroundContainer); | ||
const dummyRootItemConfig = ResolvedRowOrColumnItemConfig.createDefault('row'); | ||
this._dummyGroundContentItem = new GroundItem(this._layoutManager, dummyRootItemConfig, this._dummyGroundContainer); | ||
@@ -49,0 +48,0 @@ this.createDragListener(); |
import { UnexpectedUndefinedError } from '../errors/internal-error'; | ||
import { ComponentItem } from '../items/component-item'; | ||
import { ContentItem } from '../items/content-item'; | ||
import { Stack } from '../items/stack'; | ||
@@ -79,7 +78,4 @@ import { LayoutManager } from '../layout-manager'; | ||
/** @internal */ | ||
get show(): boolean { return this._show; } | ||
/** @internal */ | ||
get side(): Side { return this._side; } | ||
/** @internal */ | ||
get leftRightSided(): boolean { return this._leftRightSided; } | ||
@@ -91,19 +87,6 @@ | ||
get lastVisibleTabIndex(): number { return this._tabsContainer.lastVisibleTabIndex; } | ||
/** | ||
* @deprecated use {@link (Stack:class).getActiveComponentItem} */ | ||
get activeContentItem(): ContentItem | null { | ||
const activeComponentItem = this._getActiveComponentItemEvent(); | ||
if (activeComponentItem === undefined) { | ||
return null; | ||
} else { | ||
return activeComponentItem; | ||
} | ||
} | ||
get element(): HTMLElement { return this._element; } | ||
/** @deprecated use {@link (Header:class).tabsContainerElement} */ | ||
get tabsContainer(): HTMLElement { return this._tabsContainer.element; } | ||
get tabsContainerElement(): HTMLElement { return this._tabsContainer.element; } | ||
get controlsContainerElement(): HTMLElement { return this._controlsContainerElement; } | ||
/** @deprecated use {@link (Header:class).controlsContainerElement} */ | ||
get controlsContainer(): HTMLElement { return this._controlsContainerElement; } | ||
@@ -145,3 +128,3 @@ /** @internal */ | ||
); | ||
this._show = settings.show; | ||
@@ -178,5 +161,7 @@ this._popoutEnabled = settings.popoutEnabled; | ||
this._tabDropdownButton = new HeaderButton(this, this._tabDropdownLabel, DomConstants.ClassName.TabDropdown, | ||
() => this._tabsContainer.showAdditionalTabsDropdown() | ||
); | ||
if (this._tabDropdownEnabled) { | ||
this._tabDropdownButton = new HeaderButton(this, this._tabDropdownLabel, DomConstants.ClassName.TabDropdown, | ||
() => this._tabsContainer.showAdditionalTabsDropdown() | ||
); | ||
} | ||
@@ -393,3 +378,5 @@ if (this._popoutEnabled) { | ||
private processTabDropdownActiveChanged() { | ||
setElementDisplayVisibility(this._tabDropdownButton.element, this._tabsContainer.dropdownActive); | ||
if (this._tabDropdownButton !== undefined) { | ||
setElementDisplayVisibility(this._tabDropdownButton.element, this._tabsContainer.dropdownActive); | ||
} | ||
} | ||
@@ -396,0 +383,0 @@ |
@@ -0,1 +1,2 @@ | ||
import { LayoutConfig } from './config/config'; | ||
import { ResolvedComponentItemConfig } from './config/resolved-config'; | ||
@@ -34,2 +35,29 @@ import { ComponentContainer } from './container/component-container'; | ||
/** | ||
* @param container - A Dom HTML element. Defaults to body | ||
* @param bindComponentEventHandler - Event handler to bind components | ||
* @param bindComponentEventHandler - Event handler to unbind components | ||
* If bindComponentEventHandler is defined, then constructor will be determinate. It will always call the init() | ||
* function and the init() function will always complete. This means that the bindComponentEventHandler will be called | ||
* if constructor is for a popout window. Make sure bindComponentEventHandler is ready for events. | ||
*/ | ||
constructor( | ||
container?: HTMLElement, | ||
bindComponentEventHandler?: VirtualLayout.BindComponentEventHandler, | ||
unbindComponentEventHandler?: VirtualLayout.UnbindComponentEventHandler, | ||
); | ||
/** @deprecated specify layoutConfig in {@link (LayoutManager:class).loadLayout} */ | ||
constructor(config: LayoutConfig, container?: HTMLElement); | ||
/** @internal */ | ||
constructor(configOrOptionalContainer: LayoutConfig | HTMLElement | undefined, | ||
containerOrBindComponentEventHandler?: HTMLElement | VirtualLayout.BindComponentEventHandler, | ||
unbindComponentEventHandler?: VirtualLayout.UnbindComponentEventHandler, | ||
) { | ||
super(configOrOptionalContainer, containerOrBindComponentEventHandler, unbindComponentEventHandler, true); | ||
// we told VirtualLayout to not call init() (skipInit set to true) so that Golden Layout can initialise its properties before init is called | ||
if (!this.deprecatedConstructor) { | ||
this.init(); | ||
} | ||
} | ||
/** | ||
* Register a new component type with the layout manager. | ||
@@ -36,0 +64,0 @@ * |
@@ -56,2 +56,3 @@ import { ResolvedItemConfig } from '../config/resolved-config' | ||
get id(): string { return this._id; } | ||
set id(value: string) { this._id = value; } | ||
/** @internal */ | ||
@@ -58,0 +59,0 @@ get popInParentIds(): string[] { return this._popInParentIds; } |
@@ -55,2 +55,3 @@ import { ComponentItemConfig, ItemConfig } from '../config/config'; | ||
get childElementContainer(): HTMLElement { return this._childElementContainer; } | ||
get header(): Header { return this._header; } | ||
get headerShow(): boolean { return this._header.show; } | ||
@@ -57,0 +58,0 @@ get headerSide(): Side { return this._header.side; } |
import { WidthAndHeight } from './types'; | ||
/** @internal */ | ||
export function getQueryStringParam(key: string): string | null { | ||
const matches = location.search.match(new RegExp(key + '=([^&]*)')); | ||
return matches ? matches[1] : null; | ||
} | ||
/** @internal */ | ||
export function numberToPixels(value: number): string { | ||
@@ -11,0 +5,0 @@ return value.toString(10) + 'px'; |
@@ -9,11 +9,5 @@ import { LayoutConfig } from './config/config'; | ||
import { I18nStringId, i18nStrings } from './utils/i18n-strings'; | ||
import { getQueryStringParam } from './utils/utils'; | ||
/** @public */ | ||
export class VirtualLayout extends LayoutManager { | ||
/** @internal */ | ||
private _subWindowsCreated = false; | ||
/** @internal */ | ||
private _creationTimeoutPassed = false; | ||
/** | ||
@@ -33,6 +27,15 @@ * @deprecated Use {@link (VirtualLayout:class).bindComponentEvent} and | ||
/** @internal @deprecated use while constructor is not determinate */ | ||
private _bindComponentEventHanlderPassedInConstructor = false; // remove when constructor is determinate | ||
/** @internal @deprecated use while constructor is not determinate */ | ||
private _creationTimeoutPassed = false; // remove when constructor is determinate | ||
/** | ||
* @param container - A Dom HTML element. Defaults to body | ||
*/ | ||
* @param container - A Dom HTML element. Defaults to body | ||
* @param bindComponentEventHandler - Event handler to bind components | ||
* @param bindComponentEventHandler - Event handler to unbind components | ||
* If bindComponentEventHandler is defined, then constructor will be determinate. It will always call the init() | ||
* function and the init() function will always complete. This means that the bindComponentEventHandler will be called | ||
* if constructor is for a popout window. Make sure bindComponentEventHandler is ready for events. | ||
*/ | ||
constructor( | ||
@@ -47,11 +50,18 @@ container?: HTMLElement, | ||
constructor(configOrOptionalContainer: LayoutConfig | HTMLElement | undefined, | ||
containerOrBindComponentEventHandler: HTMLElement | VirtualLayout.BindComponentEventHandler | undefined, | ||
unbindComponentEventHandler: VirtualLayout.UnbindComponentEventHandler | undefined, | ||
skipInit: true, | ||
); | ||
/** @internal */ | ||
constructor(configOrOptionalContainer: LayoutConfig | HTMLElement | undefined, | ||
containerOrBindComponentEventHandler?: HTMLElement | VirtualLayout.BindComponentEventHandler, | ||
unbindComponentEventHandler?: VirtualLayout.UnbindComponentEventHandler, | ||
skipInit?: true, | ||
) { | ||
super(VirtualLayout.createLayoutManagerConstructorParameters(configOrOptionalContainer, containerOrBindComponentEventHandler)); | ||
// More work needed to get popouts working with virtual. | ||
if (containerOrBindComponentEventHandler !== undefined) { | ||
if (typeof containerOrBindComponentEventHandler === 'function') { | ||
this.bindComponentEvent = containerOrBindComponentEventHandler; | ||
this._bindComponentEventHanlderPassedInConstructor = true; | ||
@@ -64,8 +74,27 @@ if (unbindComponentEventHandler !== undefined) { | ||
if (this.isSubWindow) { | ||
document.body.style.visibility = 'hidden'; | ||
if (!this._bindComponentEventHanlderPassedInConstructor) { | ||
// backward compatibility | ||
if (this.isSubWindow) { | ||
// document.body.style.visibility = 'hidden'; | ||
// Set up layoutConfig since constructor is not determinate and may exit early. Other functions may need | ||
// this.layoutConfig. this.layoutConfig is again calculated in the same way when init() completes. | ||
// Remove this when constructor is determinate. | ||
if (this._constructorOrSubWindowLayoutConfig === undefined) { | ||
throw new UnexpectedUndefinedError('VLC98823'); | ||
} else { | ||
const resolvedLayoutConfig = LayoutConfig.resolve(this._constructorOrSubWindowLayoutConfig); | ||
// remove root from layoutConfig | ||
this.layoutConfig = { | ||
...resolvedLayoutConfig, | ||
root: undefined, | ||
} | ||
} | ||
} | ||
} | ||
if (this.layoutConfig.root === undefined || this.isSubWindow) { | ||
this.init(); | ||
if (skipInit !== true) { | ||
if (!this.deprecatedConstructor) { | ||
this.init(); | ||
} | ||
} | ||
@@ -94,17 +123,7 @@ } | ||
override init(): void { | ||
/** | ||
* Create the popout windows straight away. If popouts are blocked | ||
* an error is thrown on the same 'thread' rather than a timeout and can | ||
* be caught. This also prevents any further initilisation from taking place. | ||
*/ | ||
if (this._subWindowsCreated === false) { | ||
this.createSubWindows(); | ||
this._subWindowsCreated = true; | ||
} | ||
/** | ||
* If the document isn't ready yet, wait for it. | ||
*/ | ||
if (document.readyState === 'loading' || document.body === null) { | ||
if (!this._bindComponentEventHanlderPassedInConstructor && (document.readyState === 'loading' || document.body === null)) { | ||
document.addEventListener('DOMContentLoaded', () => this.init(), { passive: true }); | ||
@@ -119,3 +138,3 @@ return; | ||
*/ | ||
if (this.isSubWindow === true && this._creationTimeoutPassed === false) { | ||
if (!this._bindComponentEventHanlderPassedInConstructor && this.isSubWindow === true && !this._creationTimeoutPassed) { | ||
setTimeout(() => this.init(), 7); | ||
@@ -127,3 +146,8 @@ this._creationTimeoutPassed = true; | ||
if (this.isSubWindow === true) { | ||
this.adjustToWindowMode(); | ||
if (!this._bindComponentEventHanlderPassedInConstructor) { | ||
this.clearHtmlAndAdjustStylesForSubWindow(); | ||
} | ||
// Expose this instance on the window object to allow the opening window to interact with it | ||
window.__glInstance = this; | ||
} | ||
@@ -134,3 +158,64 @@ | ||
/** | ||
* Clears existing HTML and adjusts style to make window suitable to be a popout sub window | ||
* Curently is automatically called when window is a subWindow and bindComponentEvent is not passed in the constructor | ||
* If bindComponentEvent is not passed in the constructor, the application must either call this function explicitly or | ||
* (preferably) make the window suitable as a subwindow. | ||
* In the future, it is planned that this function is NOT automatically called in any circumstances. Applications will | ||
* need to determine whether a window is a Golden Layout popout window and either call this function explicitly or | ||
* hide HTML not relevant to the popout. | ||
* See apitest for an example of how HTML is hidden when popout windows are displayed | ||
*/ | ||
clearHtmlAndAdjustStylesForSubWindow(): void { | ||
const headElement = document.head; | ||
const appendNodeLists = new Array<NodeListOf<Element>>(4); | ||
appendNodeLists[0] = document.querySelectorAll('body link'); | ||
appendNodeLists[1] = document.querySelectorAll('body style'); | ||
appendNodeLists[2] = document.querySelectorAll('template'); | ||
appendNodeLists[3] = document.querySelectorAll('.gl_keep'); | ||
for (let listIdx = 0; listIdx < appendNodeLists.length; listIdx++) { | ||
const appendNodeList = appendNodeLists[listIdx]; | ||
for (let nodeIdx = 0; nodeIdx < appendNodeList.length; nodeIdx++) { | ||
const node = appendNodeList[nodeIdx]; | ||
headElement.appendChild(node); | ||
} | ||
} | ||
const bodyElement = document.body; | ||
bodyElement.innerHTML = ''; | ||
bodyElement.style.visibility = 'visible'; | ||
this.checkAddDefaultPopinButton(); | ||
/* | ||
* This seems a bit pointless, but actually causes a reflow/re-evaluation getting around | ||
* slickgrid's "Cannot find stylesheet." bug in chrome | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const x = document.body.offsetHeight; | ||
} | ||
/** | ||
* Will add button if not popinOnClose specified in settings | ||
* @returns true if added otherwise false | ||
*/ | ||
checkAddDefaultPopinButton(): boolean { | ||
if (this.layoutConfig.settings.popInOnClose) { | ||
return false; | ||
} else { | ||
const popInButtonElement = document.createElement('div'); | ||
popInButtonElement.classList.add(DomConstants.ClassName.Popin); | ||
popInButtonElement.setAttribute('title', this.layoutConfig.header.dock); | ||
const iconElement = document.createElement('div'); | ||
iconElement.classList.add(DomConstants.ClassName.Icon); | ||
const bgElement = document.createElement('div'); | ||
bgElement.classList.add(DomConstants.ClassName.Bg); | ||
popInButtonElement.appendChild(iconElement); | ||
popInButtonElement.appendChild(bgElement); | ||
popInButtonElement.addEventListener('click', () => this.emit('popIn')); | ||
document.body.appendChild(popInButtonElement); | ||
return true; | ||
} | ||
} | ||
/** @internal */ | ||
@@ -173,68 +258,2 @@ override bindComponent(container: ComponentContainer, itemConfig: ResolvedComponentItemConfig): ComponentContainer.BindableComponent { | ||
} | ||
/** | ||
* Creates Subwindows (if there are any). Throws an error | ||
* if popouts are blocked. | ||
* @internal | ||
*/ | ||
private createSubWindows() { | ||
for (let i = 0; i < this.layoutConfig.openPopouts.length; i++) { | ||
const popoutConfig = this.layoutConfig.openPopouts[i]; | ||
this.createPopoutFromPopoutLayoutConfig(popoutConfig); | ||
} | ||
} | ||
/** | ||
* This is executed when GoldenLayout detects that it is run | ||
* within a previously opened popout window. | ||
* @internal | ||
*/ | ||
private adjustToWindowMode() { | ||
const headElement = document.head; | ||
const appendNodeLists = new Array<NodeListOf<Element>>(4); | ||
appendNodeLists[0] = document.querySelectorAll('body link'); | ||
appendNodeLists[1] = document.querySelectorAll('body style'); | ||
appendNodeLists[2] = document.querySelectorAll('template'); | ||
appendNodeLists[3] = document.querySelectorAll('.gl_keep'); | ||
for (let listIdx = 0; listIdx < appendNodeLists.length; listIdx++) { | ||
const appendNodeList = appendNodeLists[listIdx]; | ||
for (let nodeIdx = 0; nodeIdx < appendNodeList.length; nodeIdx++) { | ||
const node = appendNodeList[nodeIdx]; | ||
headElement.appendChild(node); | ||
} | ||
} | ||
const bodyElement = document.body; | ||
bodyElement.innerHTML = ''; | ||
bodyElement.style.visibility = 'visible'; | ||
if (!this.layoutConfig.settings.popInOnClose) { | ||
const popInButtonElement = document.createElement('div'); | ||
popInButtonElement.classList.add(DomConstants.ClassName.Popin); | ||
popInButtonElement.setAttribute('title', this.layoutConfig.header.dock); | ||
const iconElement = document.createElement('div'); | ||
iconElement.classList.add(DomConstants.ClassName.Icon); | ||
const bgElement = document.createElement('div'); | ||
bgElement.classList.add(DomConstants.ClassName.Bg); | ||
popInButtonElement.appendChild(iconElement); | ||
popInButtonElement.appendChild(bgElement); | ||
popInButtonElement.addEventListener('click', () => this.emit('popIn')); | ||
bodyElement.appendChild(popInButtonElement); | ||
} | ||
/* | ||
* This seems a bit pointless, but actually causes a reflow/re-evaluation getting around | ||
* slickgrid's "Cannot find stylesheet." bug in chrome | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const x = document.body.offsetHeight; | ||
/* | ||
* Expose this instance on the window object | ||
* to allow the opening window to interact with | ||
* it | ||
*/ | ||
window.__glInstance = this; | ||
} | ||
} | ||
@@ -275,3 +294,3 @@ | ||
{ | ||
const windowConfigKey = subWindowChecked ? null : getQueryStringParam('gl-window'); | ||
const windowConfigKey = subWindowChecked ? null : new URL(document.location.href).searchParams.get('gl-window'); | ||
subWindowChecked = true; | ||
@@ -281,3 +300,3 @@ const isSubWindow = windowConfigKey !== null; | ||
let containerElement: HTMLElement | undefined; | ||
let config: ResolvedLayoutConfig | undefined; | ||
let config: LayoutConfig | undefined; | ||
if (windowConfigKey !== null) { | ||
@@ -290,3 +309,8 @@ const windowConfigStr = localStorage.getItem(windowConfigKey); | ||
const minifiedWindowConfig = JSON.parse(windowConfigStr) as ResolvedPopoutLayoutConfig; | ||
config = ResolvedLayoutConfig.unminifyConfig(minifiedWindowConfig); | ||
const resolvedConfig = ResolvedLayoutConfig.unminifyConfig(minifiedWindowConfig); | ||
config = LayoutConfig.fromResolved(resolvedConfig) | ||
if (configOrOptionalContainer instanceof HTMLElement) { | ||
containerElement = configOrOptionalContainer; | ||
} | ||
} else { | ||
@@ -300,7 +324,4 @@ if (configOrOptionalContainer === undefined) { | ||
} else { | ||
if (LayoutConfig.isResolved(configOrOptionalContainer)) { | ||
config = configOrOptionalContainer as ResolvedLayoutConfig; | ||
} else { | ||
config = LayoutConfig.resolve(configOrOptionalContainer); | ||
} | ||
// backwards compatibility | ||
config = configOrOptionalContainer; | ||
} | ||
@@ -317,3 +338,3 @@ } | ||
return { | ||
layoutConfig: config, | ||
constructorOrSubWindowLayoutConfig: config, | ||
isSubWindow, | ||
@@ -320,0 +341,0 @@ containerElement, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
32617
0
1926149
25