JavaScript: Effiziente Ereignisverarbeitung

Event-Delegation

Wenn zahlreiche Elemente im Dokument überwacht werden sollen, ist es sehr aufwändig umzusetzen und langsam in der Ausführung, diese herauszusuchen, zu durchlaufen und bei jedem denselben Event-Handler zu registrieren. Bei solchen Aufgabenstellungen können Sie vom Bubbling-Effekt profitieren, das ist das Aufsteigen der Ereignisse im DOM-Baum. Man macht sich die Verschachtelung der Elemente im DOM-Baum zunutze und überwacht die Ereignisse von verschiedenen Elementen bei einem gemeinsamen, höherliegenden Element, zu dem die Ereignisse aufsteigen. Diese Technik nennt sich Event-Delegation (englisch delegation für Übertragung von Aufgaben). Dabei wird einem zentralen Element die Aufgabe übertragen, die Ereignisse zu verarbeiten, die bei seinen Nachfahrenelementen passieren.

Event-Delegation eignet sich insbesondere dann, wenn viele gleichförmige Elemente in Menüs, Link-Listen, Formularen oder Tabellen JavaScript-Interaktivität benötigen. Ohne Event-Delegation müsste man jedes Element einzeln ansprechen, um dort immer denselben Event-Handler zu registrieren.

Nehmen wir beispielsweise eine Liste mit Links zu Bildern. Wenn JavaScript aktiv ist, soll das Vollbild dokumentintern eingeblendet werden. Ein ähnliches Beispiel hatten wir bereits beim Unterdrücken der Standardaktion – die Umsetzung der Einblendung bleibt weiterhin ausgeklammert.

Wir gehen von folgendem HTML-Gerüst aus:

<ul id="bilderliste">
<li><a href="bilder/bild1.jpg"><img src="bilder/thumbnail1.jpg" alt="">
   Ebru und Robin auf dem Empire State Building</a></li>
<li><a href="bilder/bild2.jpg"><img src="bilder/thumbnail2.jpg" alt="">
   Noël und Francis vor dem Taj Mahal</a></li>
<li><a href="bilder/bild3.jpg"><img src="bilder/thumbnail3.jpg" alt="">
   Isaak und Ahmet vor den Pyramiden von Gizeh</a></li>
<!-- ... viele weitere Links mit Thumbnails ... -->
</ul>

Beim Klick auf einen der Links soll nun das verlinkte Bild eingeblendet werden. Anstatt jedem a-Element einzeln einen Handler zuzuweisen, registrieren wir ihn beim gemeinsamen Vorfahrenelement ul mit der ID bilderliste:

document.getElementById("bilderliste").onclick = bilderlistenKlick;

In der angegebenen Handler-Funktion bilderlistenKlick findet nun die Überprüfung des Zielelementes statt.

funktion bilderlistenKlick (e) {
   // Vereinheitlichung von Event-Objekt und Zielelement
   var e = e || window.event;
   var target = e.target || e.srcElement;

   var elementName = target.nodeName,
      aElement = false;
   // Überprüfe, ob das Zielelement ein Link oder ein Bild im Link ist:
   if (elementName == "A") {
      // Falls ein Link geklickt wurde, speichere das Zielelement
      // in der Variable aElement:
      aElement = target;
   } else if (elementName == "IMG") {
      // Falls das Thumbnail-Bild geklickt wurde,
      // suche das zugehörige Link-Element:
      aElement = target.parentNode;
   }

   // Zeige das Vollbild, wenn das Zielelement
   // ein Link ist oder in einem Link liegt:
   if (aElement) {
      zeigeVollbild(aElement);
      // Unterdrücke die Standardaktion:
      return false;
   }

   // Andernfalls mache nichts.
}

In dieser Funktion wird das Zielelement des Ereignisses angesprochen und dessen Elementname überprüft. Wenn ein a-Element geklickt wurde, muss es sich um einen Link auf ein Bild handeln und das Vollbild soll eingeblendet werden.

Das alleine wäre bereits mit der Abfrage if (target.nodeName == "A") zu erledigen. Das Beispiel hat allerdings bewusst eine Schwierigkeit eingebaut, um Ihnen das Event-Bubbling und das Arbeiten mit dem Zielelement näher zu bringen: In den a-Elementen liegen zusätzlich img-Elemente für die Thumbnails. Wenn der Anwender auf diese klickt, soll das Vollbild selbstverständlich ebenfalls eingeblendet werden. In dem Fall ist jedoch nicht der Link das Zielelement, sondern logischerweise das img-Element.

Aus diesem Grund muss die Abfrage erweitert werden: Handelt es sich um ein a-Element oder um ein Element, das direkt in einem a-Element liegt? Falls ein img-Element das Zielelement ist, steigen wir von diesem zu seinem a-Elternelement auf. Schließlich wird die Funktion zeigeVollbild mit dem gefundenen a-Elementobjekt als Parameter aufgerufen. Das Gerüst dieser Funktion sieht so aus:

function zeigeVollbild (aElement) {
   // Empfange das Elementobjekt als ersten Parameter und
   // lese dessen href-Attribut mit der Bild-Adresse aus:
   var bildAdresse = aElement.href;

   // Blende das Bild ein, auf das der Link zeigt.
   // (Die genaue Umsetzung ist an dieser Stelle ausgeklammert.)
}

Dieses Beispiel soll Ihnen die grundlegende Funktionsweise von Event-Delegation veranschaulichen:

  1. Es gibt eine Handler-Funktion, die alle Ereignisse eines Types überwacht, welche von seinen Nachfahrenelementen aufsteigen.
  2. Darin wird das Ereignis untersucht und insbesondere das Zielelement überprüft.
  3. Wenn das Zielelement gewissen Kriterien entspricht (z.B. einem bestimmten Elementyp oder einer Klasse angehört), wird auf das Ereignis reagiert. Das kann in dieser Funktion erfolgen oder der Übersicht halber in einer anderen.

Wie Sie schon bei diesem einfachen Beispiel sehen, ist eine aufwändige Untersuchung des DOM-Elementenbaumes rund um das Zielelement nötig. Bei Event-Delegation stellt sich oft die Frage, ob das Zielelement in einem anderen Element enthalten ist, auf das gewisse Kriterien zutreffen. Eine allgemeinere und vielseitig einsetzbare Lösung werden Sie später noch kennenlernen.

Anwendungsbereiche von Event-Delegation

... Forumsposting

Capturing

Capturing (englisch für »Einfangen«) ist eine Phase beim Event-Fluss, die wir bereits kurz angesprochen haben. Der DOM-Event-Standard definiert drei Phasen, in denen ein Ereignis durch den DOM-Elementbaum wandert (Event-Fluss) und Handler auslöst:

  1. Capturing-Phase (Absteigen zum Zielelement): Das Ereignis steigt vom obersten Dokument-Knoten im Elementenbaum hinab bis zum Zielelement des Ereignisses. Auf diesem Weg werden alle Handler ausgeführt, die für den Ereignistyp für die Capturing-Phase registriert wurden.
  2. Target-Phase (Zielelement-Phase): Das Ereignis erreicht sein Zielelement und löst die betreffenden Handler aus, die dort für die Bubbling-Phase registriert wurden.
  3. Bubbling-Phase (Aufsteigen vom Zielelement): Das Ereignis steigt ausgehend vom Zielelement wieder in der Element-Hierarchie auf. Es durchläuft alle Vorfahrenelemente und löst dort die relevanten Handler aus.

Alle bisher beschriebenen Modelle, ausgehend vom traditionellen über W3C DOM Events und dem Microsoft-Modell, haben Handler für die Bubbling-Phase registriert. Wie wir uns das Bubbling zunutze machen, haben wir bereits bei der Event-Delegation kennengelernt.

Capturing ist ein weiterer Ansatz, um Ereignisse effizienter zu überwachen: Wir können ein Event-Handler bei einem höherliegenden Element registrieren, um die Ereignisse zu überwachen, die bei vielen Nachfahrenelemente passieren.

Der Unterschied zwischen Bubbling und Capturing folgender: Nicht alle Ereignisse haben eine Bubbling-Phase, das heißt nicht alle Ereignisse steigen auf und lösen die entsprechenden Handler bei ihren Vorfahrenelementen aus. Das hat durchaus seinen Sinn, macht aber die beschriebene Event-Delegation unmöglich. Gäbe es das Event-Capturing nicht, wären Sie gezwungen, alle nicht aufsteigenden Ereignisse direkt bei ihren Zielelementen zu überwachen. Mithilfe des Event-Capturings können Sie auch solche Ereignisse zentral überwachen – denn jedes Ereignis hat eine Capturing-Phase.

Event-Capturing ist nur unter Verwendung der standardisierten Methode addEventListener möglich. Das traditionelle Event-Handling mit seinem Schema element.onevent = handlerfunktion registriert den Handler immer für die Bubbling-Phase. Dasselbe gilt für das Microsoft-Modell mit attachEvent. Für Internet Explorer vor Version 9, welche addEventListener nicht unterstützen, müssen Sie daher gegebenenfalls eine Alternative ohne Event-Capturing bereitstellen.

Um Event-Handler für die Capturing-Phase zu registrieren, nutzen Sie wie gewohnt addEventListener, setzen jedoch den dritten Boolean-Parameter auf true:

document.addEventListener("focus", captureHandler, true);

Die Vorteile des Capturings liegen also darin, insbesondere nicht aufsteigende Ereignisse bei einem höherliegenden Element zu verarbeiten.

Folgende Ereignisse beispielsweise steigen nicht auf:

Da die Capturing-Phase die erst im Event-Fluss ist, ist es möglich, ein Ereignis schon in dieser Phase abzufangen. Ruft man in der Capturing-Phase die stopPropation-Methode des Event-Objektes auf, so wird der Fluss abgebrochen und die Ziel- und Bubbling-Phase fallen aus. Das Ereignis erreicht somit das Zielelement nicht.