In Applikationen soll generell ein Kontext-Menü zur Verfügung stehen, in dem einerseits gewisse Standard-Kommandos (Bearbeiten, Löschen, ...) immer erscheinen, andererseits aber auch bereits existierende applikationsspezifische Funktionalitäten angeboten werden, die für das aktuell selektierte Objekt verfügbar sind - entweder global in der Applikation, oder spezifisch in einzelnen Views.
Der Anwendungsentwickler soll in der Lage sein, zu entscheiden, welcher der Einträge für ein spezifisches Objekt enabled/disabled sind (per Code, beispielsweise in Abhängigkeit eines Attributwertes des Objekts). Der enabled/disabled-Status muss der selbe sein, wie für das entsprechende Kommando in der Toolbar, Button-Zeile oder dem Burger-Menü.
User-Interface
Das Kontext-Menü soll einerseits per rechter Maustaste, andererseits auch auf anderem Weg für Touch-Geräte aufrufbar sein.
Funktionalitäten
Standard-Funktionalitäten sollen immer in Kontext-Menü erscheinen, müssen aber in der Applikation View-spezifisch disabled werden können (z.B. Löschen, Bearbeiten).
Weitere Funktionalitäten sollen von den Applikationen typ-spezifisch hinzugefügt werden können, und sollen dann in allen Views der Anwendung für Objekte dieses Typs zur Verfügung stehen (z.B. "Umbenennen", "Versenden (Mail)").
Weitere Funktionalitäten sollen von Applikationen typ- und view-spezifisch hinzugefügt werden können, und sollen dann in der jeweiligen View der Anwendung für Objekte dieses Typs zur Verfügung stehen.
Konfigurierbarkeit
Es soll für den Anwendungsentwickler möglich sein, existierende Kommands in Burger-Menü, Button-Zeile, Toolbar in das Kontextmenü hereinzukonfigurieren.
Der Anwendungsentwickler muss generell in der Lage sein, zu entscheiden, welcher der Einträge für ein spezifisches Objekt enabled/disabled werden sollen (per Code, beispielsweise in Abhängigkeit eines Attributwertes des Objekts). Der enabled/disabled-Status muss immer der selbe sein, wie für das entsprechende Kommando in der Icon-Leiste oder im Burger-Menü.
Umsetzung
Siehe TL/ContextMenu
Code-Migration
- Kommandos, die ein Icon haben, bisher in der Toolbar angezeigt wurden und jetzt auch im Kontext-Menü auftauchen müssen überprüft werden, ob sie auf beiden Hintergründen funktionieren. Möglicherweise muss stattdessen ein Icon-Font-Icon genutzt werden, dass seine Fordergundfarbe anpassen kann. Dies wurde für die Edit/Save/Cancel-Icons gemacht.
- Alle Methoden in BoundChecker müssen das zu prüfende Objekt als Argument erhalten und dürfen nicht implizit das aktuelle Modell (z.B. der Komponente mit einberechnen)
- BoundChecker.hideReason(Object)
- getCurrentObject(BoundCommandGroup, Object)
- Methoden an BoundChecker welche kein aktuelles Objekt übergeben bekommen, können nur noch an BoundCheckerComponent aufgerufen werden und dürfen nicht in die Berechtigungsprüfung von Kommandos eingehen (nur noch in Anzeige-Prüfung z.B. nach Tab-Wechsel)
- allow(BoundCommandGroup)
- allow(BoundCommand)
- Kommandos, die das aktuelle Komponenten-Modell nicht benötigen (z.B. "Refresh") müssen null() als target Konfiguration erhalten, damit sie nicht in das Kontext-Menü von Elementen übergeordneter Komponenten aufgenommen werden. Diese Konfiguration kann entweder über die Einstellung target="null()" bei der Konfiguration des Kommandos erfolgen oder als Default-Annotation bei der Kommando-Implementierung:
#!java public class InvalidateCommand extends AJAXCommandHandler { public interface Config extends AJAXCommandHandler.Config { @Override @FormattedDefault(TARGET_NULL) ModelSpec getTarget(); } ... }
- Dialog-Öffner, welche die Option targetComponent="..." verwenden, müssen zusätzlich als Konfiguration target="model(self())" erhalten. Da die meisten Dialoge ihr Modell nicht explizit beim Öffnen gesetzt bekommen, sondern das Modell "hinterrücks" von ihrem dialogParent() erhalten, hat sich die Default-Einstellung für target von OpenModalDialogCommandHandler von model(self()) zu null() geändert. Dies bewirkt, dass solche Dialogöffner, die nicht mit einem anderen Modell als dem aktuell in der Parent-Komponente gesetzten funktionieren, nicht in das Kontext-Menü von übergeordneten Komponenten aufgenommen werden, z.B:
#!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>
- Wird ein Dialog nicht mehr angezeigt, oder hat der Öffener die falsche Ausführbarkeit, so kann das am geänderten Default für target des OpenModalDialogCommandHandler liegen. Das ist der Fall, wenn am Open-Handler z.B. eine Executability-Rule konfiguriert wurde, die sich auf das Target-Modell des Händlers bezieht, obwohl der geöffnete Dialog gar nicht mit dem Target-Modell des Handlers arbeitet, sonder sich sein Modell "hinterrücks" von seinem Dialog-Parent besorgt. In diesem Fall muss der Dialog so umgestellt werden, dass sein Modell über targetComponent="..." am Open-Handler gesetzt wird und die Konfiguration target wie im letzten Punkt beschreiben gesetzt wird, z.B:
#!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>
Offene Punkte
-
Kontext-Menü für Tree-Komponents und Tree-Controls -
Kontext-Menü für Table-Komponents und Table-Controls -
Kontext-Menü für Tree-Table-Komponents -
Kontext-Menü für alle Komponenten-Hintergründe -
Kontext-Menü für inline dargestellte Objekte ("Resource-Renderer") -
Kontext-Menü für Charts- Nicht in diesem Ticket.
-
Typ-spezifische Kontext-Menü-Einträge -
Resource-Provider liefert Kontext-Menü?- Nein: Es gibt ein zusätzliches Interface ContextMenuCommandsProvider, mit dem für ein Objekt Kontext-Menü-Kommandos zur Verfügung gestellt werden können. Eine konfigurierte Variante davon ist der LabelProviderService.
-
Form-Gruppen zuklappen über Kontextmenü- Nein: Zu viel Kontext-Menü-Fläche reserviert für zu wenig Funktionalität.
-
Kontext-Menü für Formularfelder -
Überschrift für Kontext-Menüs -
Das Kontext-Menu öffnet sich direkt an der Maus-Position, unabhängig davon, ob das geamte Menü an dieser Position auch dargestellt werden kann. Stattdessen muss das Menu so dargestellt werden, dass es auch auf den Bildschirm passt - so wie z.B. die Filter-Popups.- Siehe #24641.
Review
-
Wenn ein Kontext-Menü bereits offen ist und man macht auf ein anderes Element einen Rechtsklick, sollte das erste Kontext-Menü geschlossen und ein neues geöffnet werden. Bisher bleibt das Kontext-Menü offen und es geht zusätzlich das Browser-Kontext-Menü auf. Das ist für mich verwirrend. Weil ich das meistens mache, wenn ich mich verklickt habe und auf einem anderen Element das Kontext-Menü aufmachen möchte.- Wenn man bei geöffnetm Kontext-Menü auf dem Hintergrund jetzt den Kontext-Menü-Click ausführt, wird das ursprüngliche Kontext-Menü geschlossen statt das Browser-Kontext-Menü zu öffnen. Leider kann man in dieser Situation nicht das Kontext-Menü des neuen Ziels direkt öffnen, da der Klick dieses Ziel nicht trifft sondern eine durchsichtige Pane die über dem kompletten Fenster liegt.
-
Wenn man auf ein Objekt mit langem Namen klickt, wird das Kontext-Menü so breit wie der Name. In manchen Anwendungen hat der Kunde aber sehr lange Namen für seine Fachobjekte. Daher sollte die Breite des Kontext-Menüs begrenzt werden.- Der Titel wird nie breiter als der Inhalt des Menüs.
-
Die Breite des Kontext-Menüs verhält sich seltsam: Beim Klick auf das Root-Objekt im Demo-Typen-Baum bricht der Text "Knoten anlegen" um. Vermutlich weil das Kontext-Menü zu schmal ist. Aber wenn ich die I18N-Keys anzeigen lasse, wird es sehr viel breiter angezeigt, ohne Text umzubrechen.- Menü-Eintäge brechen nicht mehr um.
-
Wenn man mit dem Kontext-Menü den Gui-Inspector öffnet und etwas außerhalb des Kontext-Menüs klickt, wird der Cursor zum Warte-Cursor. Erst nach einer Weile merkt man, dass nichts passiert und dass der Warte-Cursor immer außerhalb des Kontext-Menüs angezeigt wird. Das ist irritierend, betrifft aber andererseits auch nur uns. Kann man da trotzdem noch etwas verbessern? Zum Beispiel dass das Inspizieren dort auch funktioniert. Oder das der Cursor beim Inspizieren außerhalb des Kontext-Menüs sich auf "nicht möglich" ändert.- Das ist nicht anders als bei bisherigen Burger-Menüs auch. Der Cursor kann nicht kontext-sensitiv sein, weil während der Inspektion eine Pane über dem gesammten Inhalt liegt.
-
In der Grid kann ich im Kontext-Menü jeder Zeile in den Bearbeiten-Modus wechseln. Aber Abbrechen, Speichern usw. kann ich dort nicht machen. Das gibt es nur, wenn ich in den Tabellen-Header klicke. Das wirkt inkonsistent. Kannst du das noch einbauen?- In der Grid kann man zwar alle Zeilen über das Kontext-Menü bearbeiten, aber man kann natürlich nur die eine aktuell bearbeitete speichern/übernehmen/abbrechen - das ist der Unterschied. Es ist jetzt genau so eingebaut, dass man speichern/übernehmen/abbrechen nur im Kontext-Menü des gerade bearbeitetn Objektes/der bearbeitetn Zeile sieht. Das gilt nicht nur für die Grid sondern auch für Bäume mit Detail-Sicht (zumindest dann, wenn "Bearbeiten" im Kontext-Menü des Baumes auftaucht, also z.B. in Strukturen:Aspektvererbung).
-
In den Demo Tabellen-Sichten "Frozen", "Konfigurierte Sidebar" und "Baumbasierte Tabelle" kann ich das Kontext-Menü der Komponente nicht öffnen. Ich kann nur das für einzelne Zeilen öffnen. Ist das Absicht?- Das Kontext-Menü für diese Sichten hat keine Einträge. Die Tabellen-Konfigurationskommandos tauchen nicht im Kontext-Menü auf. Könnte man sich sicher auch wünschen, ist aber technisch schwierig.
-
In den Toolbars kann ich das Kontext-Menü für deren Komponenten nicht öffnen.- Ja, technisch schwierig.
-
Wenn man die Kontext-Menü-Provider mit dem Typ der Komponente parametrisiert, müssen die konkreten Ableitungen die Komponente nicht jedes mal casten.- Wenn man das täte (com.top_logic.layout.basic.contextmenu.component.ContextMenuFactory parametrisieren), dann könnte man die Methode nicht mehr aufrufen (ohne einen unchecked Cast).
-
In ComponentContextMenuFactory.Provider.createButtons(...) werden einer Liste weitere Objekte hinzugefügt. Diese Liste kommt aus super.createButtons(...). Das Problem: super gibt keine Garantien, ob die Liste mutable oder auch nur resizable ist. Die Liste wird dort mittels createProviderButtons(...) erzeugt. Das ruft wiederum toButtons(...) auf. Und das verwendet Collectors.toList(...). Und das sagt: "There are no guarantees on the type, **mutability**, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier)." In der von mir verwendeten Java-Version wird dort ArrayList::new verwendet, wodurch es funktioniert. Aber mit Listen die von woanders kommen, sollte man generell vorsichtig sein.- Ack.
-
TypeBasedContextMenuFactory.Provider._component kannst du final machen.- Ok.
-
TypeBasedContextMenuFactory.Config: Bei den Properties fehlen die Konstanten für deren @Name.- Done.
-
ContextMenuCommandsProvider: Dieses Interface hat zwei Methoden: hasContextMenuCommands und getContextCommands. Für die erste lässt sich eine triviale Default-Implementierung angeben: !getContextCommands(...).isEmpty(). Dann wäre es ein Functional-Interface und könnte per Lambda-Ausdruck implementiert werden. Bisher benötigt man dafür immer eine neue Klasse. Selbst wenn die anonym ist, sind das viel mehr Zeilen als ein Lambda-Ausdruck. Und dort wo eine Optimierung möglich ist, kann weiterhin diese Methode implementiert werden.- Das Interface hat genau deswegen zwei Methoden, weil die eine extrem häufig gerufen wird und daher schell sein muss und die andere teuer sein kann. Daher ist die triviale Default-Implementierung kontraproduktiv. Das steht auch so (vielleicht zu freundlich) in der Dokumentation: "Can be used for optimizing the decision, whether a context menu should be offered."
-
ConfiguredContextMenuCommandsProvider: Die Instanzvariablen kannst du final machen. Für das Property override fehlt die @Name Annotation. Für entries fehlt eine Konstante für den Wert der @Name Annotation.- Done.
-
GridContextMenuFactory.Provider.acceptComponentCommand(...) Die Methode ist überschrieben, ruft aber nur super auf.- Removed.
-
GridTableConfig: Im JavaDoc steht: "TabConfig with defaults for GridComponent." Du meintest bestimmt TableConfig statt TabConfig. -
Es gibt jetzt zwei Klassen TestLabelProviderService. Beide wurden in diesem Jahr eingeführt. Eine unter #24380 in com.top_logic.element. Und eine unter #24501 in com.top_logic.- Das ist Absicht, weil man Modell-Verweise nur in tl-element testen kann.
-
Folgende Instanzvariablen kannst du final machen:-
ContextCommandsControl._contextMenu und _titleProvider -
ContextMenuControl._contextMenu -
AbstractMenuContents._contents - Done.
-
Test
Bäume
Tabellen
Grids
Komponenten-Kommandos
Inline-Objekte
Formularfelder
- Für Form-Fields, Technisches Demo:Layout-Framework#1:Formulare:Form Controls (inline) das Feld textInputWithContextMenu hat ein Kontext-Menü, das vordefinierte Werte in das Feld einfügt.