Kontextmenüs

Ein Kontextmenü enthält eine Liste von Aktionen, die ein Nutzer für eine Komponente wie einen Arbeitsbereich, einen Block oder einen Arbeitsbereichskommentar ausführen kann. Das Kontextmenü wird als Reaktion auf einen Rechtsklick oder ein langes Drücken auf einem Touchgerät angezeigt. Wenn Sie das @blockly/keyboard-navigation-Plug-in verwenden, wird es auch mit einer Tastenkombination angezeigt, die standardmäßig Ctrl+Enter unter Windows oder Command+Enter unter Mac ist.

Das Standardkontextmenü für einen Block

Kontextmenüs eignen sich gut, um Aktionen hinzuzufügen, die der Nutzer nur selten ausführt, z. B. das Herunterladen eines Screenshots. Wenn Sie davon ausgehen, dass eine Aktion häufiger verwendet wird, sollten Sie sie leichter aufrufen können.

Kontextmenüs werden von Arbeitsbereichen, Blöcken, Arbeitsbereichskommentaren, Sprechblasen und Verbindungen unterstützt. Sie können sie auch in Ihren eigenen benutzerdefinierten Komponenten implementieren. Blockly bietet Standardkontextmenüs, die Sie anpassen können. Sie können Kontextmenüs in Arbeitsbereichen und Blöcken auch pro Arbeitsbereich oder pro Block anpassen.

Funktionsweise von Kontextmenüs

Blockly hat eine Registrierung, die Vorlagen für alle möglichen Menüelemente enthält. Jede Vorlage beschreibt, wie ein einzelnes Element in einem Kontextmenü erstellt wird. Wenn der Nutzer ein Kontextmenü für eine Komponente aufruft, passiert Folgendes:

  1. Fordert die Registrierung auf, ein Array von Menüelementen zu erstellen, die für die Komponente gelten. Die Registrierung fragt jede Vorlage, ob sie auf die Komponente angewendet werden kann. Wenn ja, wird dem Array ein entsprechendes Menüelement hinzugefügt.

  2. Wenn die Komponente ein Arbeitsbereich oder Block ist, wird geprüft, ob der Arbeitsbereich oder Block, in dem das Menü aufgerufen wurde, eine Funktion zum Anpassen des Kontextmenüs hat. Wenn ja, wird das Array an die Funktion übergeben, die Elemente des Arrays hinzufügen, löschen oder ändern kann.

  3. Zeigt das Kontextmenü mit dem (möglicherweise geänderten) Array von Kontextmenüelementen an.

Blockly definiert einen Standardsatz von Vorlagen für die Kontextmenüs für Arbeitsbereiche, Blöcke und Arbeitsbereichskommentare. Sie lädt die Vorlagen für Arbeitsbereiche und Blöcke in die Registrierung vor. Wenn Sie die Vorlagen für Arbeitsbereichskommentare verwenden möchten, müssen Sie sie selbst in die Registrierung laden.

Informationen zum Hinzufügen, Löschen und Ändern von Vorlagen in der Registrierung finden Sie unter Registrierung anpassen.

Umfang

Kontextmenüs werden von verschiedenen Arten von Komponenten implementiert, darunter Arbeitsbereiche, Arbeitsbereichskommentare, Verbindungen, Blöcke, Sprechblasen und Ihre eigenen benutzerdefinierten Komponenten. Die Kontextmenüs für die einzelnen Komponententypen können unterschiedliche Elemente enthalten und Elemente können sich je nach Komponententyp unterschiedlich verhalten. Daher muss das Kontextmenüsystem wissen, für welche Komponente es aufgerufen wurde.

Dazu verwendet die Registrierung ein Scope-Objekt. Die Komponente, für die das Kontextmenü aufgerufen wurde, wird in der Eigenschaft focusedNode als Objekt gespeichert, das IFocusableNode implementiert. IFocusableNode wird von allen Komponenten implementiert, auf die sich Nutzer konzentrieren können, einschließlich derer, die Kontextmenüs implementieren. Weitere Informationen finden Sie unter Fokussystem.)

Das Scope-Objekt wird an mehrere Funktionen in einer Vorlage übergeben. In jeder Funktion, die ein Scope-Objekt erhält, können Sie anhand des Typs des Objekts in der focusedNode-Eigenschaft entscheiden, was zu tun ist. Sie können beispielsweise prüfen, ob die Komponente ein Block ist:

if (scope.focusedNode instanceof Blockly.BlockSvg) {
  // do something with the block
}

Das Scope-Objekt hat weitere optionale Eigenschaften, deren Verwendung nicht mehr empfohlen wird, die aber weiterhin festgelegt werden können:

  • block wird nur festgelegt, wenn die Komponente, deren Menü angezeigt wird, ein BlockSvg ist.
  • workspace wird nur festgelegt, wenn die Komponente ein WorkspaceSvg ist.
  • comment wird nur festgelegt, wenn die Komponente ein RenderedWorkspaceComment ist.

Diese Attribute decken nicht alle Arten von Komponenten ab, die ein Kontextmenü haben können. Verwenden Sie daher vorzugsweise das Attribut focusedNode.

Der Typ „RegistryItem“

Vorlagen haben den Typ ContextMenuRegistry.RegistryItem, der die folgenden Attribute enthält. Die Attribute preconditionFn, displayText und callback schließen sich gegenseitig mit dem Attribut separator aus.

ID

Die Property id sollte ein eindeutiger String sein, der angibt, was Ihr Kontextmenüelement bewirkt.

const collapseTemplate = {
  id: 'collapseBlock',
  // ...
};

Voraussetzungsfunktion

Mit preconditionFn können Sie einschränken, wann und wie ein Kontextmenüelement angezeigt werden soll.

Es sollte einer der folgenden Strings zurückgegeben werden: 'enabled', 'disabled' oder 'hidden'.

Wert Beschreibung Bild
'enabled' Zeigt an, dass das Element aktiv ist. Eine aktivierte Option
'disabled' Gibt an, dass das Element nicht aktiv ist. Deaktivierte Option
'hidden' Blendet das Element aus.

An preconditionFn wird auch ein Scope übergeben, mit dem Sie ermitteln können, auf welcher Art von Komponente das Menü geöffnet wurde und in welchem Zustand sich diese Komponente befindet.

Sie möchten beispielsweise, dass ein Element nur für Blöcke und nur dann angezeigt wird, wenn sich diese Blöcke in einem bestimmten Zustand befinden:

const collapseTemplate = {
  // ...
  preconditionFn: (scope) => {
    if (scope.focusedNode instanceof Blockly.BlockSvg) {
      if (!scope.focusedNode.isCollapsed()) {
        // The component is a block and it is not already collapsed
        return 'enabled';
      } else {
        // The block is already collapsed
        return 'disabled';
      }
    }
    // The component is not a block
    return 'hidden';
  },
  // ...
}

Anzeigetext

displayText ist das, was dem Nutzer als Teil des Menüelements angezeigt werden sollte. Der Anzeigetext kann ein String, HTML oder eine Funktion sein, die entweder einen String oder HTML zurückgibt.

const collapseTemplate = {
  // ...
  displayText: 'Collapse block',
  // ...
};

Wenn Sie eine Übersetzung von Blockly.Msg anzeigen möchten, müssen Sie eine Funktion verwenden. Wenn Sie versuchen, den Wert direkt zuzuweisen, werden die Nachrichten möglicherweise nicht geladen und Sie erhalten stattdessen den Wert undefined.

const collapseTemplate = {
  // ...
  displayText: () => Blockly.Msg['MY_COLLAPSE_BLOCK_TEXT'],
  // ...
};

Wenn Sie eine Funktion verwenden, wird ihr auch ein Scope-Wert übergeben. Damit können Sie dem Anzeigetext Informationen zum Element hinzufügen.

const collapseTemplate = {
  // ...
  displayText: (scope) => {
    if (scope.focusedNode instanceof Blockly.Block) {
      return `Collapse ${scope.focusedNode.type} block`;
    }
    // Shouldn't be possible, as our preconditionFn only shows this item for blocks
    return '';
  },
  // ...
}

Gewicht

Die weight bestimmt die Reihenfolge, in der Kontextmenüelemente angezeigt werden. Positivere Werte werden in der Liste weiter unten angezeigt als weniger positive Werte. Sie können sich vorstellen, dass Elemente mit höheren Gewichten „schwerer“ sind und daher nach unten sinken.

const collapseTemplate = {
  // ...
  weight: 10,
  // ...
}

Die Gewichte für die integrierten Kontextmenüelemente werden in aufsteigender Reihenfolge ab 1 und jeweils um 1 erhöht.

Callback-Funktion

Die callback-Eigenschaft ist eine Funktion, die die Aktion des Kontextmenüelements ausführt. Es werden mehrere Parameter übergeben:

  • scope: Ein Scope-Objekt, das einen Verweis auf die Komponente enthält, deren Menü geöffnet ist.
  • menuOpenEvent: Das Event, das das Öffnen des Kontextmenüs ausgelöst hat. Das kann ein PointerEvent oder KeyboardEvent sein, je nachdem, wie der Nutzer das Menü geöffnet hat.
  • menuSelectEvent: Die Event, mit der dieses bestimmte Kontextmenüelement im Menü ausgewählt wurde. Dies kann ein PointerEvent oder ein KeyboardEvent sein, je nachdem, wie der Nutzer das Element ausgewählt hat.
  • location: Die Coordinate in Pixelkoordinaten, in der das Menü geöffnet wurde. So können Sie beispielsweise einen neuen Block an der Klickposition erstellen.
const collapseTemplate = {
  // ...
  callback: (scope, menuOpenEvent, menuSelectEvent, location) => {
    if (scope.focusedNode instanceof Blockly.BlockSvg) {
      scope.focusedNode.collapse();
    }
  },
}

Mit scope können Sie Vorlagen entwerfen, die je nach Komponente, in der sie geöffnet wurden, unterschiedlich funktionieren:

const collapseTemplate = {
  // ...
  callback: (scope) => {
    if (scope.focusedNode instance of Blockly.BlockSvg) {
      // On a block, collapse just the block.
      const block = scope.focusedNode;
      block.collapse();
    } else if (scope.focusedNode instanceof Blockly.WorkspaceSvg) {
      // On a workspace, collapse all the blocks.
      let workspace = scope.focusedNode;
      collapseAllBlocks(workspace);
    }
  }
}

Trennzeichen

Mit der Property separator wird eine Linie im Kontextmenü gezeichnet.

Vorlagen mit dem Attribut separator dürfen nicht die Attribute preconditionFn, displayText oder callback haben und können nur mit dem Attribut scopeType eingegrenzt werden. Aufgrund dieser Einschränkung können sie nur in Kontextmenüs für Arbeitsbereiche, Blöcke und Arbeitsbereichskommentare verwendet werden.

const separatorAfterCollapseBlockTemplate = {
  id: 'separatorAfterCollapseBlock',
  scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
  weight: 11, // Between the weights of the two items you want to separate.
  separator: true,
};

Für jedes Trennzeichen im Kontextmenü benötigen Sie eine andere Vorlage. Verwenden Sie das Attribut weight, um die einzelnen Trennzeichen zu positionieren.

Bereichstyp

Das Attribut „scopeType“ wird nicht mehr unterstützt. Bisher wurde damit bestimmt, ob ein Menüelement in einem Kontextmenü für einen Block, einen Arbeitsbereichskommentar oder einen Arbeitsbereich angezeigt werden soll. Da Kontextmenüs für andere Komponenten geöffnet werden können, ist das Attribut scopeType zu restriktiv. Verwenden Sie stattdessen preconditionFn, um die Option für die entsprechenden Komponenten ein- oder auszublenden.

Wenn Sie vorhandene Kontextmenüvorlagen haben, in denen scopeType verwendet wird, wird das Element in Blockly weiterhin nur für die entsprechende Komponente angezeigt.

const collapseTemplate = {
  // ...
  scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
  // ...
};

Registry anpassen

Sie können Vorlagen in der Registrierung hinzufügen, löschen oder ändern. Die Standardvorlagen finden Sie unter contextmenu_items.ts.

Vorlage hinzufügen

Sie können der Registry eine Vorlage hinzufügen, indem Sie sie registrieren. Dies sollte einmal beim Laden der Seite erfolgen. Das kann vor oder nach dem Einfügen des Arbeitsbereichs passieren.

const collapseTemplate = { /* properties from above */ };
Blockly.ContextMenuRegistry.registry.register(collapseTemplate);

Vorlage löschen

Sie können eine Vorlage aus der Registrierung entfernen, indem Sie die Registrierung anhand der ID aufheben.

Blockly.ContextMenuRegistry.registry.unregister('someID');

Vorlage ändern

Sie können eine vorhandene Vorlage ändern, indem Sie sie aus der Registry abrufen und dann direkt bearbeiten.

const template = Blockly.ContextMenuRegistry.registry.getItem('someID');
template?.displayText = 'some other display text';

Kontextmenüs für Blöcke deaktivieren

Standardmäßig haben Blöcke ein Kontextmenü, in dem Nutzer beispielsweise Blockkommentare hinzufügen oder Blöcke duplizieren können.

Sie können das Kontextmenü eines einzelnen Blocks so deaktivieren:

block.contextMenu = false;

Verwenden Sie in der JSON-Definition eines Blocktyps den Schlüssel enableContextMenu:

{
  // ...,
  "enableContextMenu": false,
}

Kontextmenüs nach Blocktyp oder Arbeitsbereich anpassen

Nachdem Blockly ein Array von Kontextmenüelementen generiert hat, können Sie es für einzelne Blöcke oder Arbeitsbereiche anpassen. Setzen Sie dazu BlockSvg.customContextMenu oder WorkspaceSvg.configureContextMenu auf eine Funktion, die das Array direkt ändert.

Objekte im Array, das an Blöcke übergeben wird, haben den Typ ContextMenuOption oder implementieren die Schnittstelle LegacyContextMenuOption. Objekte, die an Arbeitsbereiche übergeben werden, haben den Typ ContextMenuOption. Blockly verwendet die folgenden Eigenschaften dieser Objekte:

  • text: Der Anzeigetext.
  • enabled: Wenn false, wird das Element mit grauem Text angezeigt.
  • callback: Die Funktion, die aufgerufen werden soll, wenn auf das Element geklickt wird.
  • separator: Das Element ist ein Trennzeichen. Schließt sich mit den anderen drei Attributen gegenseitig aus.

Referenzdokumentation zu Property-Typen und Funktionssignaturen

Hier ist beispielsweise eine Funktion, die dem Kontextmenü eines Arbeitsbereichs ein Hello, World!-Element hinzufügt:

workspace.configureContextMenu = function (menuOptions, e) {
  const item = {
    text: 'Hello, World!',
    enabled: true,
    callback: function () {
      alert('Hello, World!');
    },
  };
  // Add the item to the end of the context menu.
  menuOptions.push(item);
}

Kontextmenü für ein benutzerdefiniertes Objekt anzeigen

So lassen Sie Kontextmenüs für benutzerdefinierte Komponenten einblenden:

  1. Implementieren Sie IFocusableNode oder erweitern Sie eine Klasse, die IFocusableNode implementiert. Diese Schnittstelle wird im Kontextmenüsystem verwendet, um Ihre Komponente zu identifizieren. Außerdem können Nutzer mit dem Tastaturnavigations-Plug-in zu Ihrer Komponente navigieren.
  2. Implementieren Sie IContextMenu, das die Funktion showContextMenu enthält. Diese Funktion ruft die Kontextmenüelemente aus der Registrierung ab, berechnet die Position auf dem Bildschirm, an der das Menü angezeigt werden soll, und zeigt das Menü schließlich an, wenn Elemente angezeigt werden sollen.

    const MyBubble implements IFocusableNode, IContextMenu {
      ...
      showContextMenu(menuOpenEvent) {
        // Get the items from the context menu registry
        const scope = {focusedNode: this};
        const items = Blockly.ContextMenuRegistry.registry.getContextMenuOptions(scope, menuOpenEvent);
    
        // Return early if there are no items available
        if (!items.length) return;
    
        // Show the menu at the same location on screen as this component
        // The location is in pixel coordinates, so translate from workspace coordinates
        const location = Blockly.utils.svgMath.wsToScreenCoordinates(new Coordinate(this.x, this.y));
    
        // Show the context menu
        Blockly.ContextMenu.show(menuOpenEvent, items, this.workspace.RTL, this.workspace, location);
      }
    }
    
  3. Fügen Sie einen Event-Handler hinzu, der showContextMenu aufruft, wenn der Nutzer mit der rechten Maustaste auf Ihre Komponente klickt. Das Tastaturnavigations-Plug-in bietet einen Ereignishandler, der showContextMenu aufruft, wenn der Nutzer Ctrl+Enter (Windows) oder Command+Enter (Mac) drückt.

  4. Fügen Sie der Registry Vorlagen für Ihre Kontextmenüelemente hinzu.