Fokussystem

Das Fokus-System verfolgt den Standort (Fokus) des Nutzers in einem Blockly-Editor. Sie wird von Blockly und benutzerdefiniertem Code verwendet, um zu bestimmen, welche Komponente (Block, Feld, Toolbox-Kategorie usw.) gerade den Fokus hat, und um den Fokus auf eine andere Komponente zu verschieben.

Es ist wichtig, das Fokussystem zu verstehen, damit Ihr benutzerdefinierter Code richtig damit funktioniert.

Architektur

Das Fokus-System besteht aus drei Teilen:

  • Die FocusManager ist ein Singleton, der den Fokus in Blockly koordiniert. Sie wird von Blockly und benutzerdefiniertem Code verwendet, um herauszufinden, welche Komponente den Blockly-Fokus hat, und um den Blockly-Fokus auf eine andere Komponente zu verschieben. Außerdem wird auf DOM-Fokusereignisse gewartet, der Blockly-Fokus mit dem DOM-Fokus synchronisiert und die CSS-Klassen verwaltet, die angeben, welche Komponente den Fokus hat.

    Der Fokusmanager wird hauptsächlich von Blockly verwendet. Sie wird manchmal von benutzerdefiniertem Code verwendet, um mit dem Fokus-System zu interagieren.

  • Ein IFocusableTree ist ein unabhängiger Bereich eines Blockly-Editors, z. B. ein Arbeitsbereich oder eine Toolbox. Sie besteht aus fokussierbaren Knoten wie Blöcken und Feldern. Bäume können auch Unterbäume haben. Ein Mutator-Arbeitsbereich für einen Block im Hauptarbeitsbereich ist beispielsweise ein Unterbaum des Hauptarbeitsbereichs.

    IFocusableTree wird hauptsächlich vom Fokusmanager verwendet. Wenn Sie keine benutzerdefinierte Toolbox schreiben, müssen Sie sie wahrscheinlich nicht implementieren.

  • Ein IFocusableNode ist eine Blockly-Komponente, die den Fokus haben kann, z. B. ein Block, ein Feld oder eine Toolbox-Kategorie. Fokussierbare Knoten haben ein DOM-Element, das den Knoten anzeigt und den DOM-Fokus hat, wenn der Knoten den Blockly-Fokus hat. Bäume sind ebenfalls fokussierbare Knoten. Sie können sich beispielsweise auf den Arbeitsbereich als Ganzes konzentrieren.

    Die Methoden in IFocusableNode werden hauptsächlich vom Fokusmanager aufgerufen.

    IFocusableNode selbst wird verwendet, um die Komponente darzustellen, die den Fokus hat. Wenn ein Nutzer beispielsweise ein Element im Kontextmenü eines Blocks auswählt, wird der Block als IFocusableNode an die Callback-Funktion des Elements übergeben.

    Wenn Sie benutzerdefinierte Komponenten schreiben, müssen Sie möglicherweise IFocusableNode implementieren.

Fokusarten

Das Fokus-System definiert verschiedene Arten von Fokus.

Blockly-Fokus und DOM-Fokus

Es gibt zwei Haupttypen von Fokus: Blockly-Fokus und DOM-Fokus.

  • Mit Blockly-Fokus wird angegeben, welche Blockly-Komponente (Block, Feld, Toolbox-Kategorie usw.) den Fokus hat. Sie ist für die Arbeit auf der Ebene von Blockly-Komponenten erforderlich. Mit dem Tastaturnavigations-Plug-in können Nutzer beispielsweise mit den Pfeiltasten von Komponente zu Komponente wechseln, z. B. von einem Block zu einem Feld. Ebenso erstellt das Kontextmenüsystem ein Menü, das für die aktuelle Komponente geeignet ist. Das heißt, es werden verschiedene Menüs für Arbeitsbereiche, Blöcke und Arbeitsbereichskommentare erstellt.

  • Mit DOM-Fokus wird angegeben, welches DOM-Element den Fokus hat. Sie ist für die Arbeit auf der Ebene von DOM-Elementen erforderlich. Screenreader beispielsweise präsentieren Informationen zum Element, das derzeit den DOM-Fokus hat, und mit Tabulatortasten wird der Fokus von DOM-Element zu DOM-Element verschoben.

Der Fokusmanager sorgt dafür, dass der Blockly-Fokus und der DOM-Fokus synchronisiert werden. Wenn ein Knoten (Blockly-Komponente) den Blockly-Fokus hat, hat das zugrunde liegende DOM-Element den DOM-Fokus und umgekehrt.

Aktiver und passiver Fokus

Der Blockly-Fokus wird weiter in aktiven Fokus und passiven Fokus unterteilt. Aktiver Fokus bedeutet, dass ein Knoten Nutzereingaben wie Tastendrücke empfängt. Passiver Fokus bedeutet, dass ein Knoten zuvor den aktiven Fokus hatte, ihn aber verloren hat, als der Nutzer zu einem Knoten in einem anderen Baum (z. B. vom Arbeitsbereich zur Toolbox) oder ganz aus dem Blockly-Editor gewechselt ist. Wenn der Baum wieder den Fokus erhält, erhält der passiv fokussierte Knoten wieder den aktiven Fokus.

Jeder Baum hat einen separaten Fokuskontext. Das bedeutet, dass maximal ein Knoten im Baum den Fokus haben kann. Ob der Fokus aktiv oder passiv ist, hängt davon ab, ob der Baum den Fokus hat. Es kann höchstens einen Knoten mit aktivem Fokus auf der gesamten Seite geben.

Der Fokusmanager verwendet verschiedene Hervorhebungen (CSS-Klassen) für aktiv und passiv fokussierte Knoten. So können Nutzer nachvollziehen, wo sie sich befinden und wohin sie zurückkehren.

Sitzungsspezifischer Fokus

Es gibt noch eine andere Art von Fokus, den ephemeren Fokus. Separate Workflows wie Dialogfelder oder Feldeditoren fordern vom Fokusmanager einen temporären Fokus an. Wenn der Fokusmanager den temporären Fokus gewährt, wird das Fokussystem angehalten. In der Praxis bedeutet das, dass solche Workflows DOM-Fokusereignisse erfassen und darauf reagieren können, ohne dass das Fokussystem ebenfalls darauf reagiert.

Wenn der Fokusmanager einen flüchtigen Fokus gewährt, ändert er den aktiv fokussierten Knoten in einen passiven Fokus. Es wird der aktive Fokus wiederhergestellt, wenn der sitzungsspezifische Fokus zurückgegeben wird.

Beispiele

Die folgenden Beispiele veranschaulichen, wie Blockly das Fokussierungssystem verwendet. Sie sollen Ihnen helfen, zu verstehen, wie Ihr Code in das Fokus-System passt und wie Ihr Code das Fokus-System verwenden kann.

Fokus mit der Tastatur verschieben

Angenommen, ein Block mit zwei Feldern hat den Blockly-Fokus, was durch eine Markierung (CSS-Klasse) im DOM-Element des Blocks angezeigt wird. Angenommen, der Nutzer drückt jetzt den Rechtspfeil:

  1. Das Plugin für die Tastaturnavigation:
    • Empfängt ein Tastendruckereignis.
    • Fordert das Navigationssystem (ein Teil von Blockly Core) auf, den Fokus auf die „nächste“ Komponente zu verschieben.
  2. Das Navigationssystem:
    • Fragt den Fokusmanager, welche Komponente den Blockly-Fokus hat. Der Fokusmanager gibt den Block als IFocusableNode zurück.
    • Legt fest, dass IFocusableNode ein BlockSvg ist, und berücksichtigt die Regeln für die Navigation von Blöcken, die besagen, dass der Blockly-Fokus vom Block als Ganzes zum ersten Feld im Block verschoben werden soll.
    • Weist den Fokusmanager an, den Blockly-Fokus auf das erste Feld zu verschieben.
  3. Der Fokusmanager:
    • Aktualisiert den Status, um den Blockly-Fokus auf das erste Feld zu setzen.
    • Legt den DOM-Fokus auf das DOM-Element des Felds fest.
    • Verschiebt die Highlight-Klasse vom Element des Blocks zum Element des Felds.

Fokus mit der Maus verschieben

Angenommen, der Nutzer klickt auf das zweite Feld im Block. Der Fokusmanager:

  1. Empfängt ein DOM-focusout-Ereignis für das DOM-Element des ersten Felds und ein focusin-Ereignis für das DOM-Element des zweiten Felds.
  2. Gibt an, dass das DOM-Element, das den Fokus erhalten hat, dem zweiten Feld entspricht.
  3. Aktualisiert den Status, um den Blockly-Fokus auf das zweite Feld zu setzen. Der Fokusmanager muss den DOM-Fokus nicht festlegen, da der Browser dies bereits getan hat.
  4. Verschiebt die Markierungsklasse vom Element des ersten Felds zum Element des zweiten Felds.

Weitere Beispiele

Hier sind einige weitere Beispiele:

  • Wenn ein Nutzer einen Block aus der Toolbox in den Arbeitsbereich zieht, erstellt der Mausereignishandler einen neuen Block und ruft den Fokus-Manager auf, um den Blockly-Fokus auf diesen Block zu setzen.

  • Wenn ein Block gelöscht wird, ruft seine dispose-Methode den Fokusmanager auf, um den Fokus auf das übergeordnete Element des Blocks zu verschieben.

  • Tastenkombinationen verwenden IFocusableNode, um die Blockly-Komponente zu identifizieren, auf die sich die Tastenkombination bezieht.

  • Kontextmenüs verwenden IFocusableNode, um die Blockly-Komponente zu identifizieren, für die das Menü aufgerufen wurde.

Anpassungen und das Fokussystem

Wenn Sie Blockly anpassen, müssen Sie darauf achten, dass Ihr Code korrekt mit dem Fokus-System funktioniert. Sie können das Fokussierungssystem auch verwenden, um den aktuell fokussierten Knoten zu identifizieren und festzulegen.

Benutzerdefinierte Blöcke und Toolbox-Inhalte

Die gängigste Methode zum Anpassen von Blockly besteht darin, benutzerdefinierte Blöcke zu definieren und den Inhalt der Toolbox anzupassen. Keine dieser Aktionen hat Auswirkungen auf das Fokussierungssystem.

Benutzerdefinierte Klassen

Benutzerdefinierte Klassen müssen möglicherweise eine oder beide Fokus-Schnittstellen (IFocusableTree und IFocusableNode) implementieren. Das ist nicht immer offensichtlich.

Einige Klassen müssen Fokus-Schnittstellen implementieren. Dazu gehören:

  • Eine Klasse, die eine benutzerdefinierte Toolbox implementiert. Diese Klasse muss IFocusableTree und IFocusableNode implementieren.

  • Klassen, die eine sichtbare Komponente (z. B. ein Feld oder ein Symbol) erstellen, zu der Nutzer navigieren können. Diese Klassen müssen IFocusableNode implementieren.

Einige Klassen müssen IFocusableNode implementieren, auch wenn sie keine sichtbare Komponente erstellen oder eine sichtbare Komponente erstellen, zu der Nutzer nicht navigieren können. Dazu gehören:

  • Klassen, die eine Schnittstelle implementieren, die IFocusableNode erweitert.

    Das Symbol zum Verschieben im Tastaturnavigations-Plug-in ist beispielsweise ein Vier-Wege-Pfeil, der darauf hinweist, dass der Block mit den Pfeiltasten verschoben werden kann. Das Symbol selbst ist nicht sichtbar (der Vierwegpfeil ist eine Blase) und Nutzer können nicht darauf zugreifen. Das Symbol muss jedoch IFocusableNode implementieren, da Symbole IIcon implementieren und IIcon IFocusableNode erweitert.

  • Klassen, die in einer API verwendet werden, für die ein IFocusableNode erforderlich ist.

    Mit der Klasse FlyoutSeparator wird beispielsweise eine Lücke zwischen zwei Elementen in einem Flyout erstellt. Es werden keine DOM-Elemente erstellt, sodass es keine sichtbare Komponente gibt und Nutzer nicht darauf zugreifen können. Es muss jedoch IFocusableNode implementieren, da es in einem FlyoutItem gespeichert ist und der FlyoutItem-Konstruktor ein IFocusableNode erfordert.

  • Klassen, die eine Klasse erweitern, die IFocusableNode implementiert.

    Beispiel: ToolboxSeparator erweitert ToolboxItem, das IFocusableNode implementiert. Trennzeichen in der Symbolleiste haben zwar eine sichtbare Komponente, Nutzer können aber nicht darauf zugreifen, da sie nicht interaktiv sind und keinen nützlichen Inhalt haben.

Andere Klassen erstellen sichtbare Komponenten, zu denen der Nutzer navigieren kann, müssen aber IFocusableNode nicht implementieren. Dazu gehören:

  • Klassen, die eine sichtbare Komponente erstellen, die ihren eigenen Fokus verwaltet, z. B. ein Feldeditor oder ein Dialogfeld. Beachten Sie, dass solche Klassen beim Start einen temporären Fokus erhalten und ihn beim Beenden zurückgeben müssen. Wenn Sie WidgetDiv oder DropDownDiv verwenden, wird dies für Sie erledigt.)

Schließlich gibt es einige Klassen, die nicht mit dem Fokus-System interagieren und IFocusableTree oder IFocusableNode nicht implementieren müssen. Dazu gehören:

  • Klassen, die eine sichtbare Komponente erstellen, zu der Nutzer nicht navigieren oder mit der sie nicht interagieren können und die keine Informationen enthält, die ein Screenreader verwenden könnte. Ein rein dekorativer Hintergrund in einem Spiel.

  • Klassen, die in keinerlei Zusammenhang mit dem Fokus-System stehen, z. B. Klassen, die IMetricsManager oder IVariableMap implementieren.

Wenn Sie sich nicht sicher sind, ob Ihre Klasse mit dem Fokus-System interagiert, testen Sie sie mit dem Tastaturnavigations-Plug-in. Wenn das nicht funktioniert, müssen Sie möglicherweise IFocusableTree oder IFocusableNode implementieren. Wenn das gelingt, Sie sich aber immer noch nicht sicher sind, lesen Sie den Code, der Ihre Klasse verwendet, um zu sehen, ob eine der beiden Schnittstellen erforderlich ist oder ob es andere Interaktionen gibt.

Fokus-Schnittstellen implementieren

Am einfachsten implementieren Sie IFocusableTree oder IFocusableNode, indem Sie eine Klasse erweitern, die diese Schnittstellen implementiert. Wenn Sie beispielsweise eine benutzerdefinierte Toolbox erstellen, erweitern Sie Toolbox, das IFocusableTree und IFocusableNode implementiert. Wenn Sie ein benutzerdefiniertes Feld erstellen, erweitern Sie Field, das IFocusableNode implementiert. Achten Sie darauf, dass Ihr Code nicht mit dem Fokus-Schnittstellencode in der Basisklasse in Konflikt gerät.

Wenn Sie eine Klasse erweitern, die eine Fokus-Schnittstelle implementiert, müssen Sie in der Regel keine Methoden überschreiben. Die häufigste Ausnahme ist IFocusableNode.canBeFocused. Sie müssen sie überschreiben, wenn Nutzer nicht zu Ihrer Komponente navigieren sollen.

Weniger häufig ist es erforderlich, die Fokus-Callback-Methoden (onTreeFocus und onTreeBlur in IFocusableTree und onNodeFocus sowie onNodeBlur in IFocusableNode) zu überschreiben. Wenn Sie versuchen, den Fokus (Aufruf von FocusManager.focusNode oder FocusManager.focusTree) über diese Methoden zu ändern, wird eine Ausnahme ausgelöst.

Wenn Sie eine benutzerdefinierte Komponente von Grund auf neu schreiben, müssen Sie die Fokus-Schnittstellen selbst implementieren. Weitere Informationen finden Sie in der Referenzdokumentation zu IFocusableTree und IFocusableNode.

Nachdem Sie die Klasse implementiert haben, testen Sie sie mit dem Tastaturnavigations-Plug-in, um zu prüfen, ob Sie zu Ihrer Komponente navigieren können.

Fokusmanager verwenden

Einige benutzerdefinierte Klassen verwenden den Fokusmanager. Die häufigsten Gründe hierfür sind, den aktuell fokussierten Knoten abzurufen und einen anderen Knoten zu fokussieren. Rufen Sie Blockly.getFocusManager auf, um den Fokusmanager abzurufen:

const focusManager = Blockly.getFocusManager();

Rufen Sie getFocusedNode auf, um den aktuell fokussierten Knoten abzurufen:

const focusedNode = focusManager.getFocusedNode();
// Do something with the focused node.

Um den Fokus auf einen anderen Knoten zu verschieben, rufen Sie focusNode auf:

// Move focus to a different block.
focusManager.focusNode(myOtherBlock);

Wenn Sie den Fokus auf einen Baum verschieben möchten, rufen Sie focusTree auf. Dadurch wird der Fokus auch auf den Stammknoten des Baums gesetzt.

// Move focus to the main workspace.
focusManager.focusTree(myMainWorkspace);

Ein weiterer häufiger Grund für die Verwendung des Fokusmanagers ist das Erfassen und Zurückgeben von temporärem Fokus. Die Funktion takeEphemeralFocus gibt ein Lambda zurück, das Sie aufrufen müssen, um den temporären Fokus zurückzugeben.

const returnEphemeralFocus = focusManager.takeEphemeralFocus();
// Do something.
returnEphemeralFocus();

Wenn Sie WidgetDiv oder DropDownDiv verwenden, wird der sitzungsspezifische Fokus für Sie verwaltet.

Tabstopps

Das Fokussystem setzt einen Tabstopp (tabindex von 0) für das Stammelement aller Bäume (den Hauptarbeitsbereich, die Toolbox und Flyout-Arbeitsbereiche). So können Nutzer mit der Tabulatortaste durch die Hauptbereiche eines Blockly-Editors navigieren und dann (mit dem Tastaturnavigations-Plug-in) mit den Pfeiltasten innerhalb dieser Bereiche navigieren. Ändern Sie diese Tabstopps nicht, da dies die Fähigkeit des Fokusmanagers beeinträchtigt, sie zu verwalten.

Sie sollten generell vermeiden, Tabstopps für andere DOM-Elemente festzulegen, die von Blockly verwendet werden, da dies die Verwendung der Tabulatortaste zum Navigieren zwischen den Bereichen des Editors und der Pfeiltasten innerhalb dieser Bereiche beeinträchtigt. Außerdem funktionieren solche Tabstopps möglicherweise nicht wie vorgesehen. Das liegt daran, dass für jeden fokussierbaren Knoten ein DOM-Element als fokussierbares Element deklariert wird. Wenn Sie einen Tabstopp für ein untergeordnetes Element des fokussierbaren Elements festlegen und der Nutzer zu diesem Element wechselt, verschiebt der Fokusmanager den DOM-Fokus auf das deklarierte fokussierbare Element.

Es ist sicher, Tabstopps für Elemente in Ihrer Anwendung festzulegen, die sich außerhalb des Blockly-Editors befinden. Wenn der Nutzer mit der Tabulatortaste vom Editor zu einem solchen Element wechselt, ändert der Fokusmanager den Blockly-Fokus von aktiv zu passiv. Aus Gründen der Barrierefreiheit sollten Sie das Attribut tabindex auf 0 oder -1 festlegen, wie in der Warnung in der MDN-Beschreibung des tabindex-Attributs empfohlen.

DOM-Fokus

Aus Gründen der Barrierefreiheit sollten Anwendungen die Methode focus für DOM-Elemente nicht aufrufen. Das ist für Nutzer von Screenreadern verwirrend, da sie plötzlich an eine unbekannte Stelle in der Anwendung verschoben werden.

Ein weiteres Problem besteht darin, dass der Fokusmanager auf Fokusereignisse reagiert, indem er den DOM-Fokus auf den nächsten übergeordneten oder untergeordneten Knoten des fokussierten Elements setzt, der als fokussierbares Element deklariert ist. Dies kann sich vom Element unterscheiden, für das focus aufgerufen wurde. Wenn kein nächster fokussierbarer Vorfahr oder das Element selbst vorhanden ist, z. B. wenn focus für ein Element außerhalb des Blockly-Editors aufgerufen wird, ändert der Fokusmanager den aktiv fokussierten Knoten einfach in den passiven Fokus.

Positionierbare Elemente

Positionierbare Elemente sind Komponenten, die über dem Arbeitsbereich positioniert werden und IPositionable implementieren. Beispiele sind der Papierkorb und der Rucksack im Backpack-Plug-in. Positionierbare Elemente sind noch nicht in das Fokus-System integriert.