golden-layout-sprotty
v2.1.0
Published
A multi-screen javascript Layout manager (Branch of golden-layout that adds some additional events)
Downloads
1
Maintainers
Readme
Golden Layout
Table of Contents
Features
- ~~Full touch support~~
- Native popup windows
- Completely themeable
- Comprehensive API
- Powerful persistence
- Works in modern browsers (Firefox, Chrome)
- Reponsive design
Installation / Usage
Library
Golden Layout is shipped via NPM. Use the following commands to install it into an application package:npm i golden-layout
Source
The source can be installed by cloning the repository at:https://github.com/golden-layout/golden-layout
To build the distribution locally, open a shell at the golden-layout directory/folder and run the following commands:
npm install
ornpm ci
(recommended) to install required dependenciesnpm run build
to generate the distribution (dist
subfolder). This script will:- delete the existing
lib
anddist
folders - compile the TypeScript code
- generate the rolled up TypeScript definition files (
index.d.ts
andgolden-layout-untrimmed.d.ts
) - generate source map
- copy the style files to the
dist
folder
- delete the existing
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 itnpm 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/
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:
- Make sure that the
golden-layout
package is not included as a dependency in the application's package - Run the
npm link
from a shell in the golden-layout source repository top level folder. - 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.
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
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.
Composable Hook
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) => void,
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.getComponentEvent = (container, itemConfig) => {
const { componentType } = itemConfig;
if (typeof componentType !== 'string') throw new Error('Invalid component type.');
createComponent(componentType, container.element);
}
goldenLayout.releaseComponentEvent = 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
<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
When attaching a component, all Golden Layout does is provide the HTML Element of a container: Container.element
. Components can use this element to bind to that Golden Layout container. For example, the component's top most HTML element could be attached as a child to this container element.
The LayoutManager.getComponentEvent
event will be fired whenever a container is created and needs a component. The LayoutManager.releaseComponentEvent
event will be fired before a container is destroyed 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.
this._goldenLayout = new GoldenLayout(goldenLayoutHostElement);
this._goldenLayout.getComponentEvent = (container, itemConfig) => {
const component = this.createFrameworkComponent(itemConfig);
// component.element is the top most HTML element in the component
container.element.appendChild(component.element);
this._containerMap.set(container, component);
}
this._goldenLayout.releaseComponentEvent = (container, component) => {
// do this if you need to dispose resources
const component = this._containerMap.get(container);
this.disposeFrameworkComponent(component);
this._containerMap.delete(container);
}
Alternatively, container.element
could be used as the top most HTML element in the component. Example code for this could look like:
this._goldenLayout = new GoldenLayout(goldenLayoutHostElement);
this._goldenLayout.getComponentEvent = (container, itemConfig) => {
const component = this.createFrameworkComponent(itemConfig, container.element);
this._containerMap.set(container, component);
}
this._goldenLayout.releaseComponentEvent = (container, component) => {
// do this if you need to dispose resources
const component = this._containerMap.get(container);
this.disposeFrameworkComponent(component);
this._containerMap.delete(container);
}
Once the LayoutManager.getComponentEvent
and (optionally) LayoutManager.releaseComponentEvent
events have been set up, functions that create components can be used. For example:
LayoutManager.loadLayout()
LayoutManager.addComponent()
Also note that if LayoutManager.getComponentEvent
is set up, you should not register any components with the register functions:
LayoutManager.registerComponent()
LayoutManager.registerComponentConstructor()
LayoutManager.registerComponentFactoryFunction()
LayoutManager.registerComponentFunction()
LayoutManager.registerGetComponentConstructorCallback()
LayoutManager.getComponentEvent
is the recommended approach for binding components to Golden Layout. The above component register functions, were mainly included in Golden Layout for backwards compatibility.
Notes
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:
- The code has been ported to TypeScript
- 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 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 - All classes, interfaces, functions and properties are marked as either
internal
orpublic
. Onlypublic
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)
Features implemented but not ready for production
Some features have been ported to TypeScript but are not yet ready for production. These features are:
- Touch Support - Improvements are required in accessing browser Touch/Drag APIs. Also some conceptual aspects of the implementation need to be improved. These will be carried out in a future release.
- Some API functions - While most API functions have been ported, not all have been tested. The APIs used in the Test Application (both apitest app and Angular example) have been tested and are ready for production. Other API functions should work but please take this warning into account.
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.
The API changes include 2 new events to support creation of components: getComponentEvent
and releaseComponentEvent
. With these events, it is far easier to integrate GoldenLayout into frameworks. This example application demonstrates how to integrate GoldenLayout into Angular:
https://github.com/golden-layout/golden-layout-ng-app
Config
Configs are now strongly typed. In addition, GoldenLayout now has "Configs" and "Resolved Configs"
- 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 isLayoutConfig.saveLayout()
which returns a "Resolved Config". - 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:
ItemConfig
This specifies the config for a content item.LayoutConfig
(previously theConfig
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:
LayoutManager.registerComponent()
(deprecated)LayoutManager.registerComponentConstructor()
LayoutManager.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:
minifyConfig()
ReplacesLayoutManager.minifyConfig()
unminifyConfig()
ReplacesLayoutManager.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
GoldenLayout is now a distinct class which is a descendant of the LayoutManager class. Your application should always create an instance of this class.
The GoldenLayout constructor takes one optional parameter: the HTML element which contains the GoldenLayout instance. If this is not specified, GoldenLayout will be placed under body
.
Note that the initial Layout is no longer specified in this constructor. Instead it is loaded with LayoutManage.loadLayout()
(see below).
LayoutManager changes
- Do not construct an instance of LayoutManager. Construct an instance of GoldenLayout (see above).
registerComponentConstructor()
(new function)
Same as previousregisterComponent()
however only used when registering a component constructor.registerComponentFactoryFunction
(new function)
Same as previousregisterComponent()
however only used when registering a call back function (closure) for creating components.- Do not use
registerComponent()
. Use the newregisterComponentConstructor()
orregisterComponentFactoryFunction()
instead. getComponentEvent
(new event)
Generate a component needed by GoldenLayout. The parameters specify its container andItemConfig
. Use this event instead ofregisterComponentConstructor()
orregisterComponentFactoryFunction
if you want to control the disposal of the component.releaseComponentEvent
(new event)
Use in conjunction withgetComponentEvent
to release/dispose any component created for GoldenLayout- Do not call
init()
. CallLayoutManager.loadLayout()
instead. loadLayout()
(new function)
Will load the new layout specified in itsLayoutConfig
parameter. This can also be subsequently called whenever the GoldenLayout layout is to be replaced.saveLayout()
(new function)
Saves the current layout as aLayoutConfig
. Replaces the existingtoConfig()
function.- Do not uses
minifyConfig()
ofunminifyConfig()
functions. Use the respective functions inResolvedLayoutConfig
. - Do not call
toConfig()
. CallLayoutManager.saveLayout()
instead. setSize()
(new function)
Sets the size of the GoldenLayout instance in pixels. Replaces the existingupdateSize()
function.- Do not use
updateSize()
. Use the newLayoutManager.setSize()
instead. rootItem
(new property) Specifies the root content item of the layout (not the Ground content item).- Do not use
root
. This has been replaced with the internal propertygroundItem
. You probably want to use the newrootItem
instead. 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
orblur
events will be emitted as appropriate unless thesuppressEvent
parameter is set to true.clearComponentFocus()
which removes any existing component item focus. If focus is removed, ablur
event will be emitted unless thesuppressEvent
parameter is set to true.
Content Items
AbstractContentItem
has been renamed toContentItem
config
property has been removed. Use the toConfig() method instead (as recommended in the original GoldenLayout documentation).- Some of the previous
config
properties such asid
andtype
are now available as properties ofContentItem
or its descendants (where appropriate). id
now has typestring
. (It used to bestring | string[]
.)ItemContainer
has been renamed toComponentContainer
Component
has been renamed toComponentItem
. "Component" now refers to the external component hosted inside GoldenLayoutRoot
has been renamed toGroundItem
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 withLayoutManager.rootItem
.Stack.getActiveContentItem()
andStack.setActiveContentItem()
have been renamed to respectiveStack.getActiveComponentItem()
andStack.setActiveComponentItem()
ContentItem.select()
andContentItem.deselect()
have been removed. Use the newComponentItem.focus()
andComponentItem.blur()
instead.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, afocus
event will be emitted (unless suppressEvent parameter is set to true).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, ablur
event will be emitted (unless suppressEvent parameter is set to true).
ComponentContainer
element
(new property - replacesgetElement()
)
Returns HTMLElement which hosts component- Do not use
getElement()
. Use the newelement
property instead initialState
(new getter)
Gets the componentState of theComponentItemConfig
used to create the contained component.stateRequestEvent
(new event)
If set,stateRequestEvent
is fired whenever GoldenLayout wants the latest state for a component. CallingLayoutManager.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 insetState()
will be saved.beforeComponentRelease
(new EventEmitter event)beforeComponentRelease
is emitted on the container before a component is released. Components can use this event to dispose of resources.- Do not use
getState()
unless you are using the deprecatedsetState()
. UseComponentContainer.initialState
getter if you have migrated to the newComponentContainer.stateRequestEvent
. setState()
has been marked as deprecated. If possible, use the newstateRequestEvent
event instead.replaceComponent()
allows you to replace a component in a container without otherwise affecting the layout.
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
- All DOM events are now propagated so that they can be handled by parents or globally.
- preventDefault() is only called by MouseMove listener used in DragListener. All other event listeners are added with passive: true.
- Bubbling Events are now emitted with the parameter EventEmitter.BubblingEvent (or descendant)
- 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
undefined
is used instead ofnull
for new properties, events etc. Some internals have also been switched to useundefined
instead ofnull
. Existing properties usingnull
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:
index.d.ts
which contains only public API elements. Applications should use this declaration file to access the library.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.