JavaScript: Arbeiten mit dem Event-Objekt

Zugriff auf das Event-Objekt

Durch das Registrieren von Event-Handlern wird die angegebene Funktion immer dann ausgeführt, wenn das jeweilige Ereignis beim jeweiligen Element eintritt. In dieser Handler-Funktion ist es meistens nötig, auf die näheren Umstände des Ereignisses zu reagieren. Beispielsweise sind bei einem Mausklick die Koordinaten des Mauszeigers interessant oder bei einem Tastendruck die gedrückte Taste.

All diese Informationen sind in JavaScript beim Event-Objekt gespeichert. Dieses Objekt repräsentiert das individuelle Ereignis, das der Handler gerade verarbeitet. Es bietet zahlreiche Eigenschaften mit Informationen zum Ereignis und einige Methoden, um das Verhalten des Ereignisses zu steuern. Wenn Sie bei der Ereignisverarbeitung diese Daten benötigen, ist der Zugriff auf das Event-Objekt die erste Aufgabe in der Handler-Funktion.

In den meisten Browsern gestaltet sich dieser Zugriff einfach: Das Event-Objekt wird der Handlerfunktion automatisch als erster Parameter übergeben. Sie muss dieses nur noch entgegen nehmen, der Parametername ist dabei frei wählbar. Üblicherweise wird ein Kurzname wie e oder ev verwendet. Für das folgende Beispiel wählen wir den sprechenden Namen eventObjekt:

function handlerfunktion (eventObjekt) {
   window.alert("Es ist ein Ereignis vom Typ " + eventObjekt.type + " passiert.");
   // Fehler im Internet Explorer < 9!
}

Diesen Zugriff auf das Event-Objekt unterstützen alle relevanten Browser. Lediglich ältere Internet Explorer vor der Version 9 unterstützen diese Technik nicht. Für diese Browserversionen ist eine Sonderlösung notwendig. Diese Internet Explorer übergeben das Event-Objekt nicht als Parameter an die Handlerfunktion, sondern stellen es unter dem globalen Objekt window.event zur Verfügung. Auch wenn es den Anschein hat: Dort ist das Event-Objekt nicht dauerhaft gespeichert, sondern window.event verweist lediglich auf das jeweilige Event-Objekt des Ereignisses, das gerade im jeweiligen Handler verarbeitet wird.

Um browserübergreifend auf das Event-Objekt zuzugreifen, ist also eine Vereinheitlichung notwendig. Diese ist recht einfach: Wir prüfen, ob der Funktion ein Parameter übergeben wurde und somit die lokale Variable eventObjekt einen Wert hat. Falls dies zutrifft, nehmen wir diesen Parameter als Event-Objekt. Andernfalls speichern wir in der bisher leeren Variable eine Referenz auf window.event.

function handlerfunktion (eventObjekt) {
   // Vereinheitlichung:
   if (!eventObjekt) {
      // Korrektur für den Internet Explorer < 9
      eventObjekt = window.event;
   }

   // Browserübergreifender Zugriff:
   window.alert("Es ist ein Ereignis vom Typ " + eventObjekt.type + " passiert.");
}

Nach der Vereinheitlichung steht das Event-Objekt browserübergreifend in einer Variable zu Verfügung.

Mit if (!eventObjekt) wird geprüft, ob der Wert der Variablen bei einer Umwandlung in den Typ Boolean den Wert false ergibt. Eine solche Abfrage ist hier möglich, weil eventObjekt entweder ein Objekt enthält oder, falls der Handlerfunktion nichts übergeben wird, mit dem Wert undefined initialisiert wird. Dieser ergibt in Boolean umgewandelt false.

Eine gleichwertige Alternativ-Schreibweise nutzt den ||-Operator. Intern funktioniert dies wie die besagte if-Anweisung : Es wird geprüft, ob ein Funktionsparameter übergeben wurde. Falls nicht, wird versucht, das Event-Objekt über window.event anzusprechen. Das Ziel ist ebenfalls vereinheitlichter Zugriff auf das Event-Objekt über die Variable eventObjekt.

function handlerfunktion (eventObjekt) {
   eventObjekt = eventObjekt || window.event;
   window.alert("Es ist ein Ereignis vom Typ " + eventObjekt.type + " passiert.");
}

Der Oder-Operator || überprüft, ob der Wert links true ergibt, also der Parameter eventObjekt gesetzt wurde. Wenn dies der Fall ist, ergibt der Ausdruck den Wert von eventObjekt und es wird quasi eventObjekt = eventObjekt ausgeführt. Dabei passiert selbstverständlich nichts, die Variable wird mit sich selbst überschrieben.

Interessant ist der andere Fall, wenn eventObjekt im Internet Explorer den Wert undefined hat, weil kein Wert für den Parameter übergeben wurde (siehe oben). Dann ist das Ergebnis des Ausdrucks der Wert rechts vom ||-Operator. Somit wird die Zuweisung eventObjekt = window.event ausgeführt. Durch diese Oder-Verzweigung ist das Event-Objekt in jedem Fall in der Variable eventObjekt gespeichert.

Welche der Schreibweise Sie verwenden, bleibt ihnen überlassen, denn sie erfüllen dieselbe Funktion. Die erste ist klarer und leicht verständlich, die zweite ist kürzer, erfordert jedoch das Verständnis des ||-Operators.

In den obigen Beispielen wird das Event-Objekt in der Variable mit dem sprechenden Namen eventObjekt gespeichert. Die Namenswahl bleibt selbstverständlich Ihnen überlassen. Es hat sich eingebürgert, diese Variable der Kürze halber e zu nennen, um Tipparbeit zu sparen. Wenn in einer Handler-Funktion eine Variable e auftaucht, dann ist darin in der Regel das Event-Objekt gespeichert. Sie könnten gleichermaßen schreiben:

function handlerfunktion (e) {
   e = e || window.event;
   window.alert("Es ist ein Ereignis vom Typ " + e.type + " passiert.");
}

Unterdrücken der Standardaktion des Ereignisses

Viele Ereignisse im Dokument haben eigentümliche Auswirkungen. Ein Beispiel: Wenn der Anwender auf einen Link klickt, so tritt ein click-Ereignis ein. Das bringt den Browser dazu, dem Link zu folgen und zum angegebenen Linkziel (der URI) zu navigieren. Das bedeutet, dass der Browser die Ressource vom Webserver herunterlädt und anzeigt. Ein weiteres Beispiel: Das Aktivieren eines Absende-Buttons eines Formulars löst ein submit-Ereignis aus, das zur Übertragung des Formulars an den Webserver führt.

Der Browser behandelt also standardmäßig gewisse Ereignisse und führt die sogenannte Standardaktion (englisch default action) aus, ohne dass der Seitenautor eine entsprechende JavaScript-Logik definiert hat.

Beim Unobtrusive JavaScript versieht man z.B. bestehende Hyperlinks mit einer JavaScript-Logik. Die ursprüngliche Funktionalität des Links will man dann zumeist unterbinden: Beim Klick auf den Link soll nur das Script ausgeführt werden, nicht mehr das Linkziel angesprungen werden.

Angenommen, wir haben folgenden Link:

<a href="bilder/bild.jpg" id="vollbildlink">Bild in Originalgröße ansehen</a>

Mit JavaScript soll diesem Link nun ein click-Handler hinzugefügt werden, der das verlinkte Bild im aktuellen Dokument einblendet, anstatt das Dokument durch das Bild auszuwechseln und das Bild einzeln anzuzeigen. Wie dieses Einblenden umgesetzt wird, interessiert uns an dieser Stelle nicht, sondern nur das Unterdrücken der Standardaktion.

Im traditionellen Event-Handling wird die Standardaktion unterdrückt, indem die Handler-Funktion false als Ergebnis zurückgibt. Am Ende der Funktion wird daher die Anweisung return false; notiert.

function zeigeVollbild () {
   // Blende das Bild ein, auf das der Link zeigt.
   // ... (an dieser Stelle uninteressant) ...

   // Unterdrücke schließlich die Standardaktion:
   return false;
}

// Registriere Event-Handler
document.getElementById("vollbildlink").onclick = zeigeVollbild;

Beachten Sie, dass mit der return-Anweisung die Funktion beendet wird. Code, der auf diese Anweisung folgt, wird nicht ausgeführt. Es sei denn, die return-Anweisung ist z.B. durch eine if-Anweisung gekapselt und wird nicht in jedem Fall ausgeführt.

Wenn Sie kein return false notieren, führt der Browser automatisch die Standardaktion aus. Sie müssen ihn also nicht mit einem return true oder auf andere Art dazu bringen – sie können die Standardaktion lediglich verhindern.

Neben return false gibt es modernere Techniken, um die Standardaktion zu verhindern. Der DOM-Standard, auf den wir später noch zu sprechen kommen, bietet eine Methode namens preventDefault beim Event-Objekt, mit der sich die Standardaktion unterdrücken lässt. Das obige Beispiel könnte auch folgendermaßen aussehen:

function zeigeVollbild (eventObjekt) {
   // Browserübergreifender Zugriff auf das Event-Objekt
   if (!eventObjekt) eventObjekt = window.event;

   // Unterdrücke die Standardaktion durch Aufruf von preventDefault:
   e.preventDefault();
   // Fehler im Internet Explorer < 9!

   // Blende das Bild ein, auf das der Link zeigt.
   // ... (an dieser Stelle uninteressant) ...
};

Der Vorteil von preventDefault ist, dass es im Gegensatz zu return false auch mitten in der Handler-Funktion aufgerufen werden kann, ohne sie gleichzeitig zu beenden. Das Beispiel demonstriert dies.

Der Nachteil ist, dass der Internet Explorer diese standardisierte Methode erst ab Version 9 kennt. Er hat jedoch eine gleichwertige Boolean-Eigenschaft des Event-Objekts namens returnValue. Weist man dieser den Wert false zu, so wird die Standardaktion unterbunden. Um auch ältere Internet Epxlorer zu unterstützen, kann die Existenz der preventDefault-Methode abgefragt werden. Existiert diese nicht, wird alternativ die Eigenschaft returnValue gesetzt:

function zeigeVollbild (eventObjekt) {
   if (!eventObjekt) eventObjekt = window.event;

   // Existiert die Methode preventDefault? Dann rufe sie auf.
   if (eventObjekt.preventDefault) {
      // W3C-DOM-Standard
      eventObjekt.preventDefault();
   } else {
      // Andernfalls setze returnValue
      // Microsoft-Alternative für Internet Explorer < 9
      eventObjekt.returnValue = false;
   }

   // Blende das Bild ein, auf das der Link zeigt.
   // ... (an dieser Stelle uninteressant) ...
};

Diese Vorgehensweise sei hier der Vollständigkeit halber erwähnt. Wenn sie Ihnen unnötig kompliziert erscheint, so können Sie sich mit dem herkömmlichen return false zufrieden geben, das die Aufgabe hinreichend erfüllt. Sie müssen allerdings beachten, dass mit return false die Handler-Funktion beendet wird.

Der Event-Fluss: Bubbling

Bisher haben wir erfahren, dass Ereignisse bei bestimmten Elementen passieren. Dort können wir sie überwachen, indem wir Handler registrieren. Tritt das Ereignis bei diesem Element ein, wird die Handler-Funktion ausgelöst.

Die Wirklichkeit ist etwas komplizierter. Die Verarbeitung eines Ereignisses verläuft in drei Phasen, die nacheinander durchlaufen werden. Davon lernen wir nun eine zweite kennen. Die dritte ist weniger wichtig und braucht Sie zum Einstieg erst einmal nicht interessieren – Sie finden ihre Beschreibung unter Capturing beschrieben.

Ein Ereignis passiert bei einem Element, dem sogenannten Zielelement (englisch target element), und löst dort alle Handler aus, die für das entsprechende Ereignis registriert wurden – soweit waren wir bereits. Diese bereits bekannte Phase nennt sich entsprechend Ziel-Phase.

Mit dieser Phase ist die Ereignis-Verarbeitung nicht zuende, denn anschließend steigt das Ereignis im DOM-Elementenbaum auf. Dieser Vorgang nennt sich Bubbling. Das Wort ist abgleitet von bubble, Englisch für Blase. Bubbling ist demnach das Aufsteigen der Events wie beispielsweise Luftblasen im Wasser.

Dieses Aufsteigen bedeutet, dass die entsprechenden Handler auch beim Eltern-Element des Zielelements ausgeführt werden, dann bei dessen Eltern-Element und so weiter, bis das Ereignis schließlich den obersten document-Knoten erreicht hat. Das Ereignis bewegt sich also nach oben im Elementbaum, durchläuft alle Vorfahrenelemente des Zielelements und löst auf diesem Weg alle entsprechenden Handler aus. Dieser Vorgang wird entsprechend Bubbling-Phase genannt.

Das mag für den Anfang unverständlich klingen, der Sinn und die Funktionsweise des Bubblings sind aber schnell erfasst. Nehmen wir folgenden HTML-Code:

<p id="absatz">
   Dies ist ein Beispiel-Element mit einem
   <strong id="wichtig">
      wichtigen Text
   </strong>.
</p>

Nehmen wir ferner an, dass das p-Element auf traditionelle Weise einen click-Handler bekommt:

function klickverarbeitung () {
   window.alert("Der Absatz wurde geklickt!");
}
document.getElementById("absatz").onclick = klickverarbeitung;

Das p-Element wird vom Browser als rechteckige Box dargestellt. Bei einem Klick irgendwo in diese Box soll die Handler-Funktion ausgeführt werden.

Wenn der Anwender auf das Wort »Beispiel-Element« klickt, ist das p-Element das Zielelement des Ereignisses. Wenn man hingegen auf »wichtigen Text« klickt, so ist das strong-Element das Zielelement des Ereignisses, nicht das p-Element! Denn dieser Text liegt in erster Linie im strong-Element und nur indirekt im p-Element. Aus Sicht des DOM-Baumes ist der Text ein Textknoten, der ein Kindknoten des strong-Elementknotens ist.

Nichtsdestoweniger erwartet man, dass ein Klick auf die Box des strong-Elements ebenfalls den click-Handler beim p-Element auslöst. Und dies ist auch der Fall – dafür sorgt das Bubbling! Das Ereignis, das ursprünglich beim strong-Element passiert ist, steigt nämlich auf, sodass der Handler des p-Elements ausgeführt wird.

Das Bubbling ist also meist erwünscht, damit bei einem Element Ereignisse überwacht werden können, selbst wenn sie ursprünglich bei Kindelementen passieren. Wenn Sie aber nicht damit rechnen, dass Ereignisse aufsteigen, so kann das Bubbling zu einiger Verwirrung führen und Sie werden sich wundern, woher plötzlich gewisse Ereignisse stammen.

Nicht alle Ereignisse steigen auf, denn für manche Ereignisse wäre es kontraproduktiv, wenn sie zentrale Handler auslösen würden.

Verarbeitendes Element und Zielelement

Durch das beschriebene Bubbling ist es möglich, dass sich das Element, bei dem ein Ereignis ursprünglich passiert ist, von dem unterscheiden kann, dessen Handler gerade aufgerufen wird. Es ist möglich, dass das Element, das das Ereignis verarbeitet, im DOM-Elementenbaum oberhalb vom Zielelement liegt. Das Ereignis steigt in dem Fall vom Zielelement auf und löst bei einem anderen Element die Handler-Funktion aus.

In vielen Fällen will man in der Handler-Funktion auf beide beteiligten Elemente zugreifen, sofern sie sich unterscheiden.

Beginnen wir mit dem Zugriff auf das verarbeitende Element, bei dem die Handler-Funktion registriert wurde: Das Element kann in der Handler-Funktion über das Schlüsselwort this angesprochen werden, denn die Handler-Funktion wird im Kontext dieses Elementobjektes ausgeführt.

Das obige Beispiel wird wieder aufgegriffen:

<p id="absatz">
   Dies ist ein Beispiel-Element mit einem
   <strong id="wichtig">
         wichtigen Text
   </strong>.
</p>

Dem Absatz wird wieder ein click-Handler zugewiesen:

function klickverarbeitung () {
   window.alert("Element vom Typ " + this.nodeName + " wurde geklickt!");
}
document.getElementById("absatz").onclick = klickverarbeitung;

Innerhalb der Handler-Funktion können wir über this auf das p-Element zugreifen. Im Beispiel wird auf dessen Eigenschaft nodeName ausgegeben, welche den Elementnamen P enthält.

Der DOM-Standard sieht eine andere Zugriffsweise vor: Die Eigenschaft currentTarget beim Event-Objekt enthält das Element, dessen Handler gerade ausgeführt wird. Der Internet Explorer kennt diese Eigenschaft erst an Version 9. Das besagte this ist in älteren Internet-Explorer-Versionen die einzige Möglichkeit, auf das fragliche Element zuzugreifen. Der Einfachheit halber können Sie browserübergreifend this verwenden.

Der eindeutige Zugriff auf das Zielelement gestaltet sich etwas schwieriger. Der DOM-Standard definiert die Eigenschaft target beim Event-Objekt. Diese kennen alle modernen Browser, doch der Internet Explorer unterstützt diese Eigenschaft erst ab Version 9. Ältere Versionen kennen eine äquivalente Eigenschaft namens srcElement. Mithilfe einer Fähigkeitenweiche nehmen wir eine Vereinheitlichung vor, sodass das Zielelement in allen Browsern über eine Variable ansprechbar ist – wir kennen dieses Vorgehensweise bereits vom Zugriff auf das Event-Objekt.

function klickverarbeitung (eventObjekt) {
   if (!eventObjekt) eventObjekt = window.event;

   // Zugriff auf das Zielelement
   if (eventObjekt.target) {
      // W3C-DOM-Standard
      var target = eventObjekt.target;
   } else {
      // Microsoft-Alternative für Internet Explorer < 9
      var target = eventObject.srcElement;
   }

   window.alert(
      "Das Ereignis passierte ursprünglich beim Element " + target.nodeName +
      " und wird vom Element " + this.nodeName + " verarbeitet.");
   );
}

Falls die Eigenschaft target des Event-Objektes gefüllt ist, legen wir in der lokalen Variable target eine Referenz darauf an. Andernfalls, das betrifft den Internet Explorer, wird die Eigenschaft srcElement verwendet.

Wie beim Zugriff auf das Event-Objekt erlaubt der ||-Operator eine Kurzschreibweise. Das Event-Objekt wird zudem unter dem Kurznamen e gespeichert. So kommen wir zu einem Schema, dem viele Handler-Funktionen entsprechen:

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

   // Nutzlast
   window.alert(
      "Das Ereignis passierte ursprünglich beim Element " + target.nodeName +
      " und wird vom Element " + this.nodeName + " verarbeitet.");
   );
}

Interaktives Beispiel

Das folgende Beispiel ist ein div-Element, an dem ein mouseover-Handler registriert wurde. In dem div-Element sind verschiedene Elemente verschachtelt. Bewegen Sie die Maus über deren Boxen, so werden mouseover-Ereignisse an diesen ausgelöst. Sie steigen im DOM-Baum auf und werden beim gemeinsamen div-Elemente verarbeitet. Unter dem div werden das Zielelement und das verarbeitende Element des Ereignisses ausgegeben. Das verarbeiten Element ist immer dasselbe, nämlich das div-Containerelement.

Ein einfacher Textabsatz ohne weiteres.

  1. Listenelement mit wichtigem Text.
  2. Listenelement mit hervorgehobenem Text.

Hier erfolgt die Ausgabe.

Der JavaScript-Quellcode dieses Beispiels sieht so aus:

var targetArea = document.getElementById('target-area');
var output = document.getElementById('target-output');
targetArea.onmouseover = function (e) {
   e = e || window.event;
   var target = e.target || e.srcElement;
   output.innerHTML = "Das <code>mouseover</code>-Ereignis passierte ursprünglich beim Element " +
      "<strong><code>" + target.nodeName + "</code></strong> und wird vom Element " +
      "<strong><code>" + this.nodeName + "</code></strong> verarbeitet.";
};

Der Code geht davon aus, dass zwei Elemente mit den IDs target-area und target-output definiert sind und das Script später im Dokument notiert ist, sodass es Zugriff auf die Elemente hat.

Kontrolle über den Event-Fluss: Bubbling verhindern

Es gibt Fälle, in denen das Bubbling nicht gewünscht ist. Beispielsweise wenn zwei verschachtelte Elemente dasselbe Ereignis überwachen, aber nur der Handler des inneren Elements aktiv werden soll, wenn dieses das Ziel des Ereignisses ist. In einer Handler-Funktion können Sie deshalb das weitere Aufsteigen des Ereignisses im Elementenbaum verhindern.

Folgendes bekannte Beispiel mit verschachtelten Elementen soll dies illustrieren:

<p id="absatz">
   Dies ist ein Beispiel-Element mit einem
   <strong id="wichtig">
      wichtigen Text
   </strong>.
</p>

Das strong-Element steckt hier im p-Element. Bei beiden Elementen wird ein click-Handler registriert:

function absatzKlick () {
   window.alert("Klick auf das p-Element");
}
document.getElementById("absatz").onclick = absatzKlick;

function wichtigKlick () {
   window.alert("Klick auf das strong-Element");
}
document.getElementById("wichtig").onclick = wichtigKlick;

Bei einem Klick auf die Fläche des strong-Elements (»wichtigen Text«) werden beide Handler-Funktionen ausgeführt, denn das Ereignis steigt vom strong-Element zum p-Element auf.

Dieses Aufsteigen können Sie in der Handler-Funktion des strong-Elementes (wichtigKlick) verhindern. Der DOM-Standard definiert dafür die Methode stopPropagation (englisch: stoppe die Verbreitung bzw. Weitergabe des Ereignisses) beim Event-Objekt. Ein Aufruf dieser Methode unterbricht den Event-Fluss und verhindert damit das (weitere) Aufsteigen.

Der Internet Explorer kennt diese Methode erst ab Version 9. Ältere Versionen verfügen über eine gleichwertige Boolean-Eigenschaft beim Event-Objekt. Diese trägt den Namen cancelBubble (englisch: breche das Aufsteigen ab). Weisen Sie dieser Eigenschaft den Wert true zu, um das Aufsteigen des Ereignisses abzubrechen.

Wieder einmal nutzen wir eine Fähigkeitenerkennung, die die Verfügbarkeit der standardisierten Methode stopPropagation prüft und im Fehlerfalle auf die Microsoft-Alternative cancelBubble zurückfällt.

Die Handler-Funktion wichtigKlick wird wie folgt modifiziert:

function wichtigKlick (eventObekt) {
   if (!eventObjekt) eventObjekt = window.event;

   // Stoppe das Aufsteigen des Ereignisses
   if (eventObjekt.stopPropagation) {
      // W3C-DOM-Standard
      eventObjekt.stopPropagation();
   } else {
      // Microsoft-Alternative für Internet Explorer < 9
      eventObjekt.cancelBubble = true;
  }

   window.alert("Klick auf das strong-Element. Das Aufsteigen des Ereignisses wird unterbunden!");
}

Damit können verschachtelte Elemente denselben Ereignistyp überwachen, im Beispiel click. Obwohl das eine Element in dem anderen enthalten ist und üblicherweise in dessen Grenzen dargestellt wird, übernimmt es die Ereignis-Verarbeitung selbstständig. Der Handler des äußeren Elements, im Beispiel absatzKlick beim p-Element, wird nur bei Klicks ausgeführt, die auf seine Fläche zielen, ausgenommen die Fläche des inneren Elements.