golden-layout: Closable, Draggable, CloseExplicitly and Edge Drop Blocked
A number of issues have been raised concerning the closability and draggability of component items and stacks. These include:
As discussed in #647 (near end), the current logic in Golden Layout for closing and dragging is a bit confusing and needs to be cleaned up. The first step in cleaning up is determining how it should work. Below, are my thoughts on how closing and dragging (and dropping) should work.
Component Item
You can specify whether a component is closable or draggable. This can be declaratively specified in config with properties closable and draggable or changed dynamically on an existing component item.
Stack
A stack is closable if:
- All its component items are closable
- It is not the only child of a row or column which has one or more edges drop blocked (more info below).
If a stack’s close button is clicked but it is not closable, then all closable component items will be closed. If all component items are closable, then the stack will be emptied but not closed.
Currently if a stack is emptied, it is automatically closed. This behaviour will be controlled with a new stack property, closeExplicitly. By default, this is false, indicating that the stack can be automatically closed. So, when its last/only component is closed or dragged off the stack (and the stack is closable), the stack will close. If closeExplicitly is set to true, then the stack will not close when the last component item is closed or dragged away. It will remain in place but empty. To remove it, you explicitly need to click its close button.
If you click on a stack’s close button while holding down the SHIFT key, then the stack will be emptied (as much as possible) instead of closed.
Currently a stack itself is not draggable. So, it does not need a draggable property. If it was draggable, it may not make that much sense having a draggable property. A non-draggable stack could effectively still be moved by dragging other stacks so that the non-draggable stack ends up being moved as if it were dragged.
Instead of flagging stacks as not draggable, we can block components being dropped beyond edges of rows or columns.
Rows or Columns
Rows and columns are never visible. They just provide a mechanism for controlling the layout of stacks. They will be automatically created and destroyed to handle components being dropped at locations where new stacks are required, or stacks being destroyed when no longer needed.
Currently, when components are dragged, drop indicators show where a component can be dropped. There are no restrictions on where a component can be dropped.
I propose that restrictions be imposed. This would be done by flagging row or column edges as ‘drop blocked’. If a row or column edge is marked as ‘drop blocked’, then nothing can be dropped between that edge and the corresponding Golden Layout container edge.
If a row or column has any edge marked as drop blocked, then that row or column cannot be deleted. Also, that row or column must always have at least one child content item. If all existing child rows or columns are deleted, then it will be given an empty stack.
A row or column could also have one child component item (instead of a stack). This component item could not be dragged or removed by the user.
None of the above affects adding or removing content items programmatically. Drop blocked edges only affect where the user can drop dragged items and that user actions do not close that row or column.
Let me give some examples on how this could be used.
Distinct areas
Issue #640 requested a feature where Golden Layout could be split into 2 distinct areas: top and bottom. Components and stacks could be laid out in each of these distinct areas, but the 2 distinct areas cannot be removed.
With drop blocked edges, this would be done by defining a config which has a column with 2 rows. Each row would have all its edges marked as drop blocked. You would then be able to rearrange icons with drag and drop within these 2 rows but never use drag and drop to create items outside these 2 distinct areas.
Header and Footer regions
This would be similar to #640 however the top row would have its left, top and right edges marked as drop blocked and the bottom row would have its left, bottom and right edges marked as drop blocked. This way header and footer would always remain however you can drop components in between.
Sidebar
You may wish to display a sidebar. This could be done by inserting a column in the root row at index 0. The column would have its top, left and bottom edges marked as drop blocked. It probably would also be given a child component item instead of a stack. In this case it could only be removed programmatically.
What are your thoughts?
The above is my view as to how to resolve issues associated with closability and draggability. This probably needs to be implemented incrementally and may involve breaking backwards compatibility.
However, it would be good to get other views before changing anything.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 3
- Comments: 16 (14 by maintainers)
“From a design perspective, interfaces create a more strong abstraction and also specify a behavior, whereas hooks/etc just override one part of the default behavior.”
A strong abstraction is great for a library. On the other hand, what applications want to be able to “override one part of the default behavior.” Pure interfaces do not help with that; you need some kind of overriding/inheritance mechanism.
“Also an interface can be extended anytime in the future to meet new requirements, whereas these hooks need more and more parameters in the config object.”
Adding new hooks to a config object is conceptually no harder than extending an interface. Admittedly, the GL library does make extending config classes somewhat tedious, perhaps due to excessive abstraction: Adding a new property to
Settingsrequires changing five different places in two files.I’m not strongly opposed to interfaces+sub-classing, but I think they might be harder to design and more complicated to use compared to hooks/callback functions - and the benefit is unclear. Both designing and using hooks has the big advantage that it can be done more incrementally: Library implementors add a hook as they see the need; application writers can use a hook as they see the use for it. Designing an interface has to be done more up-front.
Designing these factory interfaces seems relatively tricky. Note you want to make it easy to mosty use the default implementation, but just override one method/feature. In general, you don’t want people to have to copy the default implementation to (say) just add an icon. Incremental changes from the default should require incremental amount of code.
The classic Factory pattern seems pointless in a functional language such as JavaScript. I.e. no point to have a separate TabFactory class that creates a Tab. Much simpler to have a function that creates a Tab. I.e. instead of:
you have something like:
However, I’m not convinced there is a benefit in interfaces and application-specific implementation classes. For C++ and Java that is the natural way to do it, but for JavaScript/TypeScript less so.
For example, assume the Tab interface has a renderTitle method, and an application wants to override it. Contrast:
with:
Much less overhead and boilerplate with overriding functions instead of overriding classes. Is there anything you can do with an interface/sub-class design that would be difficult or impossible to do with an overriding functions design?
Also keep in mind: It is desirable (in my opinion) that using and customizing GL should be easy also if using plain JavaScript, without TypeScript.