In applications, a context menu should generally be available that, on the one hand, always displays certain standard commands (Edit, Delete, ...) always appear, but also existing application-specific functionalities that are available for the currently selected object—either globally within the application or specifically within individual views.
The application developer should be able to decide which of the entries are enabled or disabled for a specific object (via code, for example, based on an attribute value of the object). The enabled/disabled status must be the same as that for the corresponding command in the toolbar, button row, or burger menu.
User Interface
The context menu should be accessible via the right mouse button on one hand, and via other methods for touch devices on the other.
Functionality
Standard functionalities should always appear in the context menu, but it must be possible to disable them on a view-specific basis within the application (e.g., Delete, Edit).
Additional features should be able to be added by applications on a type-specific basis and should then be available in all views of the application for objects of that type (e.g., “Rename,” “Send (Email)”).
Applications should be able to add additional features on a type- and view-specific basis, and these features should then be available in the respective view of the application for objects of that type.
Configurability
It should be possible for the application developer to configure existing commands in the burger menu, button bar, and toolbar to appear in the context menu.
The application developer must generally be able to decide which of the entries for a specific object should be enabled or disabled (via code, for example, depending on an attribute value of the object). The enabled/disabled status must always match that of the corresponding command in the icon bar or the burger menu.
Implementation
See TL/ContextMenu
Code Migration
- Commands that have an icon, were previously displayed in the toolbar, and now also appear in the context menu must be checked to ensure they work on both backgrounds. It may be necessary to use an icon font icon instead, which can adapt to its background color. This was done for the Edit/Save/Cancel icons.
- All methods in BoundChecker must receive the object to be checked as an argument and must not implicitly include the current model (e.g., the component)
- BoundChecker.hideReason(Object)
- getCurrentObject(BoundCommandGroup, Object)
- Methods on BoundChecker that do not receive a current object as an argument may only be called on BoundCheckerComponent and must not be included in the permission check for commands (only in display checks, e.g., after a tab switch)
- allow(BoundCommandGroup)
- allow(BoundCommand)
- Commands that do not require the current component model (e.g., "Refresh") must be configured with `null()` as the `target ` so that they are not included in the context menu of elements in parent components. This configuration can be set either via the target="null()" setting when configuring the command or as a default annotation in the command implementation:
#!java
public class InvalidateCommand extends AJAXCommandHandler {
public interface Config extends AJAXCommandHandler.Config {
@Override
@FormattedDefault(TARGET_NULL)
ModelSpec getTarget();
}
...
}
- Dialog openers that use the option ` targetComponent="..." ` must also be configured with ` target="model(self())"`. Since most dialogs do not have their model explicitly set upon opening, but instead receive the model "behind the scenes" from their dialogParent(), the default setting for `target` in ` OpenModalDialogCommandHandler ` has changed from `model(self())` to ` null()`. As a result, dialog openers that do not work with a model other than the one currently set in the parent component will not be included in the context menu of parent components, e.g.:
#!patch
Index: webapp/WEB-INF/layouts/history/historyDialog.xml
===================================================================
--- webapp/WEB-INF/layouts/history/historyDialog.xml (revision 278883)
+++ webapp/WEB-INF/layouts/history/historyDialog.xml (working copy)
@@ -43,6 +43,7 @@
clique="history"
group="${openerCommandGroup}"
resourceKey="${defaultI18n}"
+ target="model(self())"
targetComponent="${namePrefix}Table"
>${executability}</open-handler>
</dialogInfo>
- If a dialog is no longer displayed, or if the opener has the wrong executability, this may be due to a change in the default value for ` target` of the ` OpenModalDialogCommandHandler `. This is the case, for example, if an executability rule has been configured on the open handler that refers to the handler’s target model, even though the opened dialog does not use the handler’s target model at all, but instead obtains its model "behind the scenes" from its dialog parent. In this case, the dialog must be modified so that its model is set via `targetComponent="..."` on the open handler, and the `target` configuration is set as described in the previous point, e.g.:
#!patch
Index: webapp/WEB-INF/layouts/com.top_logic.contact/admin/orgUnits/EditOrgUnit_shared.xml
===================================================================
--- webapp/WEB-INF/layouts/com.top_logic.contact/admin/orgUnits/EditOrgUnit_shared.xml (revision 278881)
+++ webapp/WEB-INF/layouts/com.top_logic.contact/admin/orgUnits/EditOrgUnit_shared.xml (working copy)
@@ -56,17 +56,23 @@
<include name="/element/createStructuredElement.xml"
detailComponent="${createComponent}"
jSPNewPage="${createJSP}"
+ model="null()"
namePrefix="${namePrefix}OrgUnit"
>
<inject>
<dialogInfo
- defaultI18n="layouts.contact.EditOrgUnit_Shared.newOrgUnit"
- executability="CreateElementRule"
height="250"
- openerClique="create"
- openerCommandGroup="Create"
width="450"
- />
+ >
+ <open-handler id="displayDialog_${namePrefix}OrgUnitnewElementDialog"
+ clique="create"
+ executability="CreateElementRule"
+ group="Create"
+ resourceKey="layouts.contact.EditOrgUnit_Shared.newOrgUnit"
+ target="model(self())"
+ targetComponent="${namePrefix}OrgUnitnewElementDialog"
+ />
+ </dialogInfo>
</inject>
</include>
</dialogs>
Open Issues
Context Menu for Tree Components and Tree ControlsContext menu for table components and table controlsContext menu for tree-table componentsContext menu for all component backgroundsContext menu for inline objects ("Resource Renderer")Context menu for charts- Not included in this ticket.
Type-specific context menu entriesDoes the Resource Provider provide a context menu?- No: There is an additional interface, ` ContextMenuCommandsProvider`, which can be used to provide context menu commands for an object. A configured variant of this is the ` LabelProviderService`.
Collapse form groups via the context menu- No: Too much context menu space is reserved for too little functionality.
Context menu for form fieldsHeader for context menusThe context menu opens directly at the mouse position, regardless of whether the entire menu can actually be displayed at that position. Instead, the menu must be displayed in a way that fits on the screen—just like the filter popups, for example.- See #24641.
Review
If a context menu is already open and you right-click on another element, the first context menu should close and a new one should open. Currently, the context menu remains open, and the browser context menu opens as well. I find this confusing. This usually happens when I accidentally click the wrong element and want to open the context menu on a different one.- If you right-click on the background while a context menu is open, the original context menu closes instead of opening the browser context menu. Unfortunately, in this situation, you can’t open the context menu for the new target directly, because the click doesn’t hit that target but instead hits a transparent pane that covers the entire window.
When you click on an object with a long name, the context menu becomes as wide as the name. However, in some applications, the customer has very long names for their business objects. Therefore, the width of the context menu should be limited.- The title never becomes wider than the menu’s content.
The width of the context menu behaves strangely: When clicking on the root object in the demo type tree, the text “Create Node” wraps. Presumably because the context menu is too narrow. But when I display the I18N keys, it appears much wider without the text wrapping.- Menu entries no longer wrap.
If you open the GUI Inspector via the context menu and click slightly outside the context menu, the cursor changes to the waiting cursor. It takes a while to realize that nothing is happening and that the waiting cursor is always displayed outside the context menu. This is annoying, but on the other hand, it only affects us. Is there still a way to improve this? For example, by making sure that inspection works there as well. Or by having the cursor change to “not possible” when inspecting outside the context menu.- This is no different from previous burger menus. The cursor can’t be context-sensitive because a panel covers the entire content during inspection.
In the grid, I can switch to edit mode via the context menu for each row. But I can’t use options like Cancel, Save, etc., there. Those options are only available when I click on the table header. That seems inconsistent. Can you add that feature?- In the grid, you can edit all rows via the context menu, but of course you can only save/apply/cancel the one you’re currently editing—that’s the difference. It’s now implemented exactly so that you only see Save/Apply/Cancel in the context menu of the object or row you’re currently editing. This applies not only to the grid but also to trees with a detail view (at least when “Edit” appears in the tree’s context menu, e.g., in Structures:Aspect Inheritance).
In the demo table views “Frozen,” “Configured Sidebar,” and “Tree-Based Table,” I can’t open the component’s context menu. I can only open it for individual rows. Is this intentional?- The context menu for these views has no entries. The table configuration commands do not appear in the context menu. This would certainly be desirable, but it is technically difficult to implement.
In the toolbars, I can’t open the context menu for their components.- Yes, it’s technically difficult.
If you parameterize the context menu providers with the component type, the specific derivations wouldn’t have to cast the component every time.- If you did that (parameterize`com.top_logic.layout.basic.contextmenu.component.ContextMenuFactory `), you would no longer be able to call the method (without an unchecked cast).
In ` ComponentContextMenuFactory.Provider.createButtons(...)`, additional objects are added to a list. This list comes from `super.createButtons(...)`. The problem: `super ` provides no guarantees as to whether the list is mutable or even resizable. The list is created there using ` createProviderButtons(...)`. This, in turn, calls `toButtons(...) `. And that uses `Collectors.toList(...)`. And that states: “There are no guarantees on the type, **mutability**, serializability, or thread safety of the returned List; if more control over the returned List is required, use toCollection(Supplier).” In the Java version I’m using, ArrayList::new is used there, which makes it work. But you should generally be cautious with lists that come from elsewhere.- Ack.
You can make `TypeBasedContextMenuFactory.Provider._component ` final.- Okay.
TypeBasedContextMenuFactory.Config: The properties are missing the constants for their @Name.- Done.
ContextMenuCommandsProvider: This interface has two methods: hasContextMenuCommands and getContextCommands. For the first one, you can specify a trivial default implementation: !getContextCommands(...).isEmpty(). Then it would be a functional interface and could be implemented using a lambda expression. So far, you’ve always needed a new class for this. Even if it’s anonymous, that’s many more lines than a lambda expression. And where optimization is possible, this method can still be implemented.- The interface has two methods precisely because one is called extremely frequently and therefore needs to be fast, while the other can be computationally expensive. Therefore, the trivial default implementation is counterproductive. This is also stated (perhaps too politely) in the documentation: “Can be used for optimizing the decision as to whether a context menu should be offered.”
ConfiguredContextMenuCommandsProvider: You can make the instance variables final. The @Name annotation is missing for the property override. For ` entries`, a constant is missing for the value of the @Name annotation.- Done.
GridContextMenuFactory.Provider.acceptComponentCommand(...) The method is overridden, but it only calls ` super`.- Removed.
GridTableConfig: The JavaDoc states: "TabConfig with defaults for GridComponent." I'm sure you meant TableConfig instead of TabConfig.There are now two TestLabelProviderService classes. Both were introduced this year. One under #24380 in com.top_logic.element, and one under #24501 in com.top_logic.- This is intentional, because model references can only be tested in tl-element.
You can mark the following instance variables as final:ContextCommandsControl._contextMenu and _titleProviderContextMenuControl._contextMenuAbstractMenuContents._contents- Done.
Test
Trees
Tables
Grids
Component Commands
Inline Objects
Form Fields
- For form fields, see Technical Demo:Layout Framework#1:Forms:Form Controls (inline); the ` textInputWithContextMenu ` field has a context menu that inserts predefined values into the field.