Golden-layout: proper way to override close button behavior in Header and Tab

Created on 17 Jul 2021  路  8Comments  路  Source: golden-layout/golden-layout

Current behavior

I'm looking for a clean way to change the close behavior in headers and tabs. I tried a bunch different ways like listening for 'close' and 'beforeItemDestroyed' events but didn't figure out how to stop the propagation to override with my behavior.

Basically, what I want to achieve this to hide the ComponentContainer instead of closing it. This way, I will display closed items in a tray and give the ability to users to restore them if needed. Need a little bit of guidance. I'll be happy to contribute back to the documentation since it's lagging behind.

Expected behavior:

Instead of calling destroy(), I'd to simply hide the ComponentContainer when user is clicking on close button in Tabs or Headers.

Codepen example:

Most helpful comment

Hi @mathpaquette

Caching components

I assume what you want is that, when the closed button is clicked, you want to store the component in a cache instead of destroying it. The cached components are shown in a tray. When a cached component is clicked, it is placed into the layout again.

You can do this now using the BindComponentEvent.

You are probably using Embedding via Registration component binding now using one of the registration functions. Instead, you can use Embedding via Events component binding.

With 'Embedding via Events', you do not use the registration functions. Instead you assign an event handler to VirtualLayout.bindComponentEvent and VirtualLayout.unbindComponentEvent. The basic ideas of using the handler are discussed in the Other Frameworks section of the readme.

However in your case, in the VirtualLayout.unbindComponentEvent event handler, you can check to see if you want the component cached. You can use (for example) a property in the component to check or maybe use container.parent.id to check. If you want it cached, place the component in a cache (say a map) and do not destroy/dispose it.

In the VirtualLayout.bindComponentEvent, you check whether the component is to be created or retrieved from the cache. You will probably want to use itemConfig.id property to identify the component instance and use that to check if it is already in the cache. If it is in the cache, remove it from the cache and return it. Otherwise create a new component and return it.

You probably want to set the virtual property to false. If this is set to true, then virtual binding is used and that requires more events to be set up.

When a user clicks on an item in the tray, use one of the addComponent() or newComponent() functions to place it back in the layout. Make sure you specify the relevant ItemConfig.id value. You may also be able to attach a DragSource to the buttons in the tray allowing them to be dragged back onto the layout (you may need to experiment with this to see if it works in your case).

Important Note: NPM does not have the latest Golden Layout release on it. Am waiting for @martin31821 to publish the release in the repository (including PR #687). The older versions of Golden Layout use the now deprecated GetComponentEvent and ReleaseComponentEvent.

Button click

You can use the itemCreated and itemDestroyed bubbling events to set up different button click handling. These will be fired whenever a ComponentItem or Stack is created or destroyed in the layout. Since these are bubbling events, you can subscribe to these events using the LayoutManager (GoldenLayout) class.

In these handlers, you could change the Tab button click handlers. This is real messy now because you probably also want to unsubscribe the existing click handlers. I should place some events on ComponentItem and Stack so that you can easily intercept these clicks and handle them yourself. This is not possible now, but could quite easily be added.

You would use code something like below to set up different click handling:

private myItemCreatedListener = (event: BubblingEvent) => this.handleMyItemCreatedListener(event);
private myItemDestroyedListener = (event: BubblingEvent) => this.handleMyItemDestroyedListener(event);

constructor() {
    this._goldlenLayout = new GoldenLayout();
    this._goldenLayout.addEventListener('itemCreated', myItemCreatedListener);
    this._goldenLayout.addEventListener('itemDestroyed', myItemDestroyedListener); // optional
}

destroy() {
    this._goldenLayout.removeEventListener('itemCreated', myItemCreatedListener);
    this._goldenLayout.removeEventListener('itemDestroyed', myItemDestroyedListener);  // optional
}

private handleMyItemCreatedListener(event: BubblingEvent) {
    // intercept clicks
}

private handleMyItemDestroyedListener(event: BubblingEvent) {
    // remove handlers if necessary
}

Documentation

We really need some kind of documentation/tutorial system set up so that people can better learn Golden Layout. Once a framework is set up for such a system, I could answer these questions by updating the documentation instead of responding with an issue comment which is hard for others to find in the future.

Do you have any experience in setting up documentation/tutorial systems? Any contribution to documentation would be welcome.

All 8 comments

@martin31821 @pbklink could you please provide a little bit of guidance here.

obviously this is not working as before in version 2.x. I was following this. https://github.com/golden-layout/golden-layout/blob/master/website/docs/Container.html#L131

like even the Stack doesn't expose Header anymore. So, I'm just trying to find a clean way to implement my use case. Please gimme some ideas.

Hi @mathpaquette

Caching components

I assume what you want is that, when the closed button is clicked, you want to store the component in a cache instead of destroying it. The cached components are shown in a tray. When a cached component is clicked, it is placed into the layout again.

You can do this now using the BindComponentEvent.

You are probably using Embedding via Registration component binding now using one of the registration functions. Instead, you can use Embedding via Events component binding.

With 'Embedding via Events', you do not use the registration functions. Instead you assign an event handler to VirtualLayout.bindComponentEvent and VirtualLayout.unbindComponentEvent. The basic ideas of using the handler are discussed in the Other Frameworks section of the readme.

However in your case, in the VirtualLayout.unbindComponentEvent event handler, you can check to see if you want the component cached. You can use (for example) a property in the component to check or maybe use container.parent.id to check. If you want it cached, place the component in a cache (say a map) and do not destroy/dispose it.

In the VirtualLayout.bindComponentEvent, you check whether the component is to be created or retrieved from the cache. You will probably want to use itemConfig.id property to identify the component instance and use that to check if it is already in the cache. If it is in the cache, remove it from the cache and return it. Otherwise create a new component and return it.

You probably want to set the virtual property to false. If this is set to true, then virtual binding is used and that requires more events to be set up.

When a user clicks on an item in the tray, use one of the addComponent() or newComponent() functions to place it back in the layout. Make sure you specify the relevant ItemConfig.id value. You may also be able to attach a DragSource to the buttons in the tray allowing them to be dragged back onto the layout (you may need to experiment with this to see if it works in your case).

Important Note: NPM does not have the latest Golden Layout release on it. Am waiting for @martin31821 to publish the release in the repository (including PR #687). The older versions of Golden Layout use the now deprecated GetComponentEvent and ReleaseComponentEvent.

Button click

You can use the itemCreated and itemDestroyed bubbling events to set up different button click handling. These will be fired whenever a ComponentItem or Stack is created or destroyed in the layout. Since these are bubbling events, you can subscribe to these events using the LayoutManager (GoldenLayout) class.

In these handlers, you could change the Tab button click handlers. This is real messy now because you probably also want to unsubscribe the existing click handlers. I should place some events on ComponentItem and Stack so that you can easily intercept these clicks and handle them yourself. This is not possible now, but could quite easily be added.

You would use code something like below to set up different click handling:

private myItemCreatedListener = (event: BubblingEvent) => this.handleMyItemCreatedListener(event);
private myItemDestroyedListener = (event: BubblingEvent) => this.handleMyItemDestroyedListener(event);

constructor() {
    this._goldlenLayout = new GoldenLayout();
    this._goldenLayout.addEventListener('itemCreated', myItemCreatedListener);
    this._goldenLayout.addEventListener('itemDestroyed', myItemDestroyedListener); // optional
}

destroy() {
    this._goldenLayout.removeEventListener('itemCreated', myItemCreatedListener);
    this._goldenLayout.removeEventListener('itemDestroyed', myItemDestroyedListener);  // optional
}

private handleMyItemCreatedListener(event: BubblingEvent) {
    // intercept clicks
}

private handleMyItemDestroyedListener(event: BubblingEvent) {
    // remove handlers if necessary
}

Documentation

We really need some kind of documentation/tutorial system set up so that people can better learn Golden Layout. Once a framework is set up for such a system, I could answer these questions by updating the documentation instead of responding with an issue comment which is hard for others to find in the future.

Do you have any experience in setting up documentation/tutorial systems? Any contribution to documentation would be welcome.

I can expose Stack.header after the current repository release is published to NPM

@pbklink thank you for this quick reply, this is very much appreciated !

Couple of things:

Hiding components

I'm already using latest v2.2.1 with LayoutManager.getComponentEvent and LayoutManager.releaseComponentEvent but it looks like that VirtualLayout events seems to be the more promising approach in the future.

I don't think caching is enough in my case because I need to persist the layout with minimized components in tray and to restore them back in their original's location. If they are getting removed from the layout, I need another mechanism to persist locations from those in the tray... I was hoping to use ComponentItem.hide() instead of having to deal with repositioning and state management. What do you think?

Documentation

Let me explore couple of options for managing the documentation. I think, what's really lacking is a good set of examples for showcasing functionalities in v2. We can get inspired by how AgGrid is combining API doc and examples. Let's see how to refresh that.

NPM from master branch

Maybe we can think about having npm packages published automatically with -next suffix when merging new code to master. This is a pretty common practice. As soon as someone merge stuff into master, packages can be installed and consumed by contributors or those who dont need an official release.

@pbklink @martin31821 should I work on the ability to override default click behavior ? I'm willing to create a PR.

Hiding Components

I gather what you want to do is hide the components while they are still in place in the layout.

Probably not a good idea to change the visibility of any Golden Layout class objects (eg. ContentItem, Stack, Tab etc). Golden Layout manages these itself and also sometimes uses their visibility as part of its layout algorithm.

I suggest you use "virtual via events" binding. With this, Golden Layout will advise your application of what a component's visibility should be (with ComponentContainer.virtualVisibilityChangeRequiredEvent), however the application can then use its own algorithm to decide what a component's visibility actually is.

NPM from master branch

@martin31821 - what do you think of the idea of using -next releases?

PR to override default click behavior

We would welcome a PR to implement this behavior. Below are my ideas regarding such a PR.

  • Add events to the relevant Golden Layout UI components so that 'click' events can be intercepted. For example, place an event on the Golden Layout Tab class to intercept clicking on the tab close button. Similar events could be added to Stack to handle clicking on the stack buttons.
  • The events should indicate whether the event has been handled. For example, the signature may be:\
    (this: void, event: MouseEvent) => boolean\
    where the boolean result indicates that the event has been handled. If it is handled, Golden Layout should not propagate this event any further. Otherwise the default event handling occurs.
  • Check to see that the Tab objects can be accessed as needed (for example expose header in Stack with a getter)
  • Please use the same coding and naming convention as used throughout Golden Layout.
  • Reliability is of paramount importance. Please ensure your changes are well tested. You may wish to enhance apitest to support this testing.

If you have any further questions, please get back to me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gigamorph picture gigamorph  路  3Comments

ButchMonkey picture ButchMonkey  路  4Comments

ldetomi picture ldetomi  路  5Comments

panyam picture panyam  路  4Comments

Raviraj531 picture Raviraj531  路  7Comments