JavaScript: Fortgeschrittene Ereignisverarbeitung

Nachteile des traditionellen Event-Handlings und Alternativen

Das traditionelle Event-Handling basiert darauf, dass ein Funktionsobjekt in einer Eigenschaft des Elementobjektes gespeichert wird. Wir erinnern uns an das Schema element.onevent = handlerfunktion.

Der Vorteil dieses Schema ist seine Einfachheit und Verständlichkeit. Will man ein Ereignis überwachen, schreibt man bloß die Handler-Funktion in eine entsprechende Element-Eigenschaft. Der größte Nachteile ist jedoch folgender: Es kann nur eine Handler-Funktion zugleich registriert werden. Denn in der Eigenschaft kann nur eine Funktion gespeichert werden, und weist man eine andere Funktion zu, überschreibt man die erste.

In manchen Fällen mag es ausreichen, dass man je Element für einen Ereignistyp nur eine Handlerfunktion definieren kann. Diese kann schließlich weitere Funktionen aufrufen, sodass nicht der gesamte auszuführende Code direkt in dieser einen Funktion stehen muss. Doch insbesondere wenn verschiedene Scripte zusammenarbeiten, besteht die Gefahr, dass sie beim traditionellen Event-Handling einander in die Quere kommen.

Es ist es durchaus möglich, mehrere Handler-Funktionen zu notieren, ohne letztlich vom traditionellen Schema abzuweichen. Dazu sind allerdings Helferscripte nötig, deren Funktionsweise nur für JavaScript-Kenner zu verstehen ist. Bevor wir auf solche »Workarounds« eingehen, wenden wir uns den fortgeschrittenen Modellen für Event-Handling zu.

Event-Handling gemäß dem W3C-Standard DOM Events

Das bisher beschriebene traditionelle Schema stammt aus den Anfangstagen von JavaScript. Der Browserhersteller und JavaScript-Erfinder Netscape erfand das Schema einst und andere Browser übernahmen es im Zuge ihrer JavaScript-Unterstützung.

Die Entwicklung ging jedoch weiter: Bei der Standardisierung des Event-Handlings verwarf das WWW-Konsortium das traditionelle Event-Handling. Der entsprechende DOM-Standard sieht ein anderes Modell vor: Alle Elementobjekte und weitere zentrale Objekte besitzen die Methode addEventListener (englisch: Ereignis-Überwacher hinzufügen). Will man dem Element einen Event-Handler zuweisen, so ruft man diese Methode auf.

Event-Handler registrieren: addEventListener

Das standardisierte Schema enthält ebenfalls die drei Bestandteile Elementobjekt, Ereignistyp und Handler-Funktion. Es lautet folgendermaßen:

element.addEventListener("event", handlerfunktion, capturing);

Die Methode erwartet also drei Parameter:

  1. Der erste Parameter ist ein String und enthält den Ereignistyp. Beispiele für den ersten Parameter sind "click", "mouseover", "load", "submit" und so weiter.
  2. Der zweite Parameter ist der Name der Handler-Funktion, genauer gesagt ein Ausdruck, der ein Funktionsobjekt ergibt.
  3. Der dritte Parameter bestimmt, für welche Event-Phase der Handler registriert werden soll. Es handelt sich um einen Boolean-Parameter, d.h. sie können true oder false notieren. false steht für die bereits bekannte Bubbling-Phase, true für die noch nicht behandelte und weniger wichtige Capturing-Phase (siehe Capturing). Die genaue Bedeutung des dritten Parameters wird erst später erklärt werden. Standardmäßig sollten Sie hier false notieren.

Das folgende Beispiel kennen wir bereits vom traditionellen Event-Handling. Beim fertigen Laden des HTML-Dokuments wird automatisch ein click-Handler bei einem Textabsatz registriert. Dieses Mal nutzen wir die standardisierte Methode addEventListener, die denselben Zweck erfüllt:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>Beispiel für Event-Handling gemäß dem W3C DOM</title>
<script type="text/javascript">

window.addEventListener("load", start, false);

function start () {
   var pElement = document.getElementById("interaktiv");
   pElement.addEventListener("click", klickverarbeitung, false);
}

function klickverarbeitung () {
   document.getElementById("interaktiv").innerHTML +=
      " Huhu, das ist von Javascript eingefügter Text.";
}

</script>
</head>
<body>

<p id="interaktiv">
   Dies ist ein einfacher Textabsatz, aber mithilfe von JavaScript können wir ihn
   interaktiv gestalten. Klicken Sie diesen Absatz doch einfach mal mit der Maus an!
</p>

</body>
</html>

Folgende Anweisungen haben sich geändert:

Aus der Zuweisung window.onload = start; ist der Aufruf window.addEventListener("load", start, false); geworden. Wenngleich window kein Elementobjekt ist, bietet es dennoch die Methode addEventListener an.

Aus der Zuweisung document.getElementById("interaktiv").onclick = klickverarbeitung; sind zwei geworden. In der ersten Anweisung speichern wir das Elementobjekt des Absatzes in einer Variable zwischen:

var pElement = document.getElementById("interaktiv");

In der zweiten Anweisung wird schließlich die Handler-Funktion registriert:

pElement.addEventListener("click", klickverarbeitung, false);

Sie können die Methoden-Aufrufe natürlich auch verketten, anstatt eine Hilfsvariable zu verwenden. Das sähe schematisch so aus: document.getElementById(…).addEventListener(…). Schließlich gibt getElementById im Regelfall ein Elementobjekt zurück, dessen Methoden Sie direkt ansprechen können. Aus Gründen der Lesbarkeit und Verständlichkeit wurde diese Kette im Beispiel in zwei Anweisungen gesplittet.

Das obige Beispiel funktioniert in allen modernen Browsern – jedoch nicht im Internet Explorer vor der Version 9. Die älteren Internet-Explorer-Versionen unterstützen den W3C-DOM-Standard noch nicht. Die Methode addEventListener ist diesen Browsern schlicht unbekannt.

Der Internet Explorer unterstützt den Events-Standard erst ab Version 9. Ältere Versionen unterstützen stattdessen ein eigenes, proprietäres Modell, das im folgenden Abschnitt vorgestellt wird.

Der Hauptvorteil von addEventListener ist, dass Sie für ein Element mehrere Handler-Funktionen für denselben Ereignistyp registrieren können. Beim obigen Beispiel können wir die start-Funktion so anpassen, dass beim p-Element zwei Handler statt bloß einer registriert werden:

function start () {
   var pElement = document.getElementById("interaktiv");
   pElement.addEventListener("click", meldung1, false);
   pElement.addEventListener("click", meldung2, false);
}

function meldung1 () {
   window.alert("Erste Handler-Funktion ausgeführt!");
}

function meldung2 () {
   window.alert("Zweite Handler-Funktion ausgeführt!");
}

Es werden zwei Handler-Funktionen namens meldung1 und meldung2 definiert. Mithilfe von addEventListener werden sie beide als click-Handler registriert. Wenn Sie auf den Textabsatz klicken, dann sollten nacheinander zwei JavaScript-Meldefenster erscheinen – und zwar in der Reihenfolge, in der die Handler mittels addEventListener registriert wurden.

Event-Handler entfernen: removeEventListener

Um die mit addEventListener registrierten Handler wieder zu entfernen, gibt es die Schwestermethode removeEventListener (englisch: Ereignis-Empfänger entfernen). Die Methode erwartet dieselben Parameter, die addEventListener beim Registrieren bekommen hat: Einen String mit dem Ereignistyp, die zu löschende Handler-Funktion und schließlich einen Boolean-Wert für die Event-Phase.

Um beide im Beispiel definierten Handler für das p-Element (nämlich meldung1 und meldung2) wieder zu entfernen, notieren wir:

function beenden () {
   pElement.removeEventListener("click", meldung1, false);
   pElement.removeEventListener("click", meldung2, false);
}

Event-Handling gemäß Microsoft für ältere Internet Explorer

Microsoft hat schon früh für seinen Internet Explorer eine Alternative zum unzureichenden traditionellen Event-Handling eingeführt, welches seinerseits vom damaligen Konkurrenten Netscape erfunden wurde. Das W3C-DOM wurde erst später standardisiert und wurde erst Jahre später im Internet Explorer 9 eingebaut. Das Microsoft-Modell wiederum wird nur vom Internet Explorer verstanden. Es handelt sich hier also um eine Sonderlösung, die nur noch für ältere Internet-Explorer-Versionen interessant ist, welche den DOM-Standard nicht umsetzen.

Microsofts Modell teilt einige Fähigkeiten mit addEventListener und removeEventListener, funktioniert im Detail jedoch anders und bringt einige Eigenheiten und Schwierigkeiten mit sich.

Event-Handler registrieren: attachEvent

Im Microsoft-Modell besitzt jedes Elementobjekt sowie einige zentrale Objekte die Methode attachEvent zum Registrieren von Event-Handlern. Das Schema lautet folgendermaßen:

element.attachEvent("onevent", handlerfunktion);

Die Methode erwartet zwei Parameter:

  1. Der erste Parameter ist ein String und enthält den Ereignistyp mit der Vorsilbe on. Beispiele für den ersten Parameter sind "onclick", "onmouseover", "onload", "onsubmit" und so weiter.
  2. Der zweite Parameter ist der Name der Handler-Funktion (ein Ausdruck, der ein Funktionsobjekt ergibt).

Wir greifen das bekannte Beispiel auf, das wir bereits nach dem traditionellem Modell und nach dem W3C-Modell umgesetzt haben, und setzen es mit dem Microsoft-Modell um:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>Beispiel für Event-Handling gemäß dem W3C DOM</title>
<script type="text/javascript">

window.attachEvent("onload", start);

function start () {
   var pElement = document.getElementById("interaktiv");
   pElement.attachEvent("onclick", klickverarbeitung);
}

function klickverarbeitung () {
   document.getElementById("interaktiv").innerHTML +=
      " Huhu, das ist von Javascript eingefügter Text.";
}

</script>
</head>
<body>

<p id="interaktiv">
   Dies ist ein einfacher Textabsatz, aber mithilfe von JavaScript können wir ihn
   interaktiv gestalten. Klicken Sie diesen Absatz doch einfach mal mit der Maus an!
</p>

</body>
</html>

Das Beispiel hat sich gegenüber dem W3C-Modell nur geringfügig geändert. Anstelle von window.addEventListener(…) wurde window.attachEvent(…) notiert, dasselbe bei pElement.attachEvent(…).

Der Ereignistyp im ersten Parameter enthält nun den Präfix on vorangestellt: Aus "load" wird "onload", aus "click" wird "onclick". Der dritte Parameter, der die Event-Phase spezifiert, fällt weg – denn Microsofts Modell unterstützt nur das Registrieren in der Bubbling-Phase.

Auch mit attachEvent können Sie verschiedene Handler für denselben Ereignistyp definieren. Das obige Beispiel wird entsprechend angepasst:

function start () {
   var pElement = document.getElementById("interaktiv");
   pElement.attachEvent("onclick", meldung1);
   pElement.attachEvent("onclick", meldung2);
}

function meldung1 () {
   window.alert("Erste Handler-Funktion ausgeführt!");
}

function meldung2 () {
   window.alert("Zweite Handler-Funktion ausgeführt!");
}

Das Beispiel enthält nichts neues, die Aufrufe von addEventListener wurden auf die besagte Weise durch attachEvent ausgetauscht.

Event-Handler entfernen: detachEvent

Auch das Microsoft-Modell bietet eine Methode, um registrierte Handler wieder zu entfernen. Sie nennt sich detachEvent und erwartet dieselben Parameter wie sein Gegenstück attachEvent.

Um die besagten click-Handler meldung1 und meldung2 wieder zu entfernen, notieren wir:

function beenden () {
   pElement.detachEvent("onclick", meldung1);
   pElement.detachEvent("onclick", meldung2);
}

Eigenheiten des Microsoft-Modell

Das Microsoft-Modell bringt eine erfreuliche und eine unerfreuliche Besonderheit mit sich:

Browserübergreifendes Event-Handling

Ausgangslage

Wir haben drei Modelle und deren Detailunterschiede kennengelernt. Das mag Sie verwirrt haben und Sie werden sich sicher fragen, welches Sie nun in der Praxis ohne Bedenken anwenden können. Das Fazit lautet leider: Keines.

addEvent-Helferfunktionen

Um relativ komfortabel browserübergreifend Ereignisse verarbeiten zu können, benötigen wir eine Helferfunktion, die eine Vereinheitlichung vornimmt und gewisse Browserunterschiede nivelliert. Eine solche alleinstehende Funktion zum Registrieren von Event-Handlern wird in der Regel addEvent genannt

Die Entwicklung einer solchen addEvent-Funktion ist eine Wissenschaft für sich. Moderne JavaScript-Bibliotheken nutzen äußerst ausgefeilte Umsetzungen. Diese basieren auf jahrelanger Forschung, um viele Sonderfälle abzudecken und dem Anwender das Event-Handling durch Vereinheitlichung aller relevanter Browserunterschiede zu vereinfachen.

Diese Bibliotheken bringen jedoch eine eigene Arbeitsweise sowie unzählige weitere Funktionen mit sich. Daher seien hier zwei isolierte Helferscripte vorgestellt.

Einfaches, oftmals ausreichendes addEvent

Ein einfaches Beispiel ist A Good Enough addEvent von Austin Matzko. Ziel ist es, mehrere Event-Handler für einen Typ bei einem Element registrieren zu können. Die Funktion verwendet addEventListener (DOM-Standard) oder attachEvent (Microsoft-Modell) je nach Verfügbarkeit.

function addEvent (obj, type, fn) {
   if (obj.addEventListener) {
      obj.addEventListener(type, fn, false);
   } else if (obj.attachEvent) {
      obj.attachEvent('on' + type, function () {
         return fn.call(obj, window.event);
      });
   }
}

Ein Anwendungsbeispiel:

<p id="beispielabsatz">Klick mich!</p>
<script type="text/javascript">
function absatzKlick () {
	alert("Der Absatz wurde geklickt!");
}
addEvent(document.getElementById("beispielabsatz"), "click", absatzKlick);
</script>

Da die beiden Modelle im Detail zueinander inkompatibel sind, sind im attachEvent-Zweig einige Anpassungen vonnöten. Diese müssen Sie im Detail nicht verstehen, sie seien hier dennoch erklärt:

  1. Die übergebene Handler-Funktion wird in einer weiteren gekapselt und eingeschlossen, die an deren Stelle als Handlerfunktion verwendet wird. Der Kontext der Handler-Funktion wird korrigiert. Dadurch ist in der Handler-Funktion mittels this der Zugriff auf das verarbeitende Element möglich. (Siehe verarbeitendes Element.)
  2. Der Handler-Funktion wird das Event-Objekt als Parameter übergeben. Dadurch fällt in der Handler-Funktion die Vereinheitlichung bem Zugriff darauf weg. (Siehe Zugriff auf das Event-Objekt.)

Kurz gesagt sorgen diese Anpassungen dafür, dass Sie browserübergreifend mglichst gleich arbeiten können und die Browserunterschiede ausgeglichen werden.

Diese addEvent-Funktion ist wie gesagt sehr einfach gehalten, was unter anderem folgende Nachteile mit sich bringt:

Flexibles und leistungsfähiges addEvent/removeEvent

Eine robustere, aber umso kompliziertere Umsetzung der Funktionen addEvent und removeEvent stammt von Dean Edwards und wurde von Tino Zijdel weiterentwickelt: addEvent() new style.

Dieses Helferscript verwendet die standardisierte Methoden addEventListener und removeEventListener in den Browsern, in denen sie zur Verfügung stehen. Als Alternative für ältere Internet Explorer wird allerdings nicht das Microsoft-eigene Modell verwendet – zu groß sind die Unterschiede zwischen den beiden Modellen. Stattdessen wird das Registrieren von mehreren Event-Handlern selbst übernommen: Es wird bei jedem Element eine eigene Liste mit Handler-Funktionen für ein Ereignistyp geführt. Beim Eintreten des Ereignisses wird eine Helferfunktion aufgerufen, die diese interne Liste abarbeitet und jede dort verzeichnete Handler-Funktion aufruft.

Neben addEvent umfasst das Script auch eine removeEvent-Methode. Im Vergleich zum einfachen addEvent werden im Internet Explorer zwei weitere Vereinheitlichungen vorgenommen, sodass folgende Techniken browserübergreifend nutzbar sind:

  1. In der Handler-Funktion ist mittels this der Zugriff auf das verarbeitende Element möglich. (Siehe verarbeitendes Element.)
  2. Der Handler-Funktion wird das Event-Objekt als Parameter übergeben. (Siehe Zugriff auf das Event-Objekt.)
  3. Zum Unterdrücken der Standardaktion kann browserübergreifend die standardisierte Methode preventDefault des Event-Objektes verwendet werden. Eine entsprechende Fähigkeiten-Weiche in der Handler-Funktion ist nicht nötig. (Siehe Unterdrücken der Standardaktion.)
  4. Um das Aufsteigen des Ereignisses zu verhindern, kann die standardisierte Methode stopPropagation des Event-Objektes browserübergreifend genutzt werden. (Siehe Bubbling verhindern.)

Falls Sie keine umfangreichere JavaScript-Bibliothek verwenden, welche ausgereiftes Event-Handling ermöglicht, so sind Sie mit diesen beiden Helferfunktionen addEvent und removeEvent gut bedient – Sie sollten sie in ihren Werkzeugkasten aufnehmen.

Browserübergreifendes Event-Handling mit Frameworks

Die besagte Browser-Vereinheitlichung berührt nur die Spitze des Eisberges: Besonders bei der Verarbeitung von Tastatur- und Maus-Ereignissen erwarten den JavaScript-Programmierer noch viel größere Browserunterschiede. Um beispielsweise browserübergreifend die Mausposition relativ zum Dokument auszulesen, ist eine komplexe Fähigkeiten-Weiche nötig. Dasselbe gilt für das Auslesen der gedrückten Taste(n). Für den JavaScript-Einsteiger ist es schwierig und unkomfortabel, für all diese Detailunterschiede Korrekturen einzubauen. Das eigentliche Verarbeiten der Ereignisse rückt durch dieses Browser-Durcheinander (im Englischen »quirks« genannt), in den Hintergrund.

Wenn Sie bei solchem Event-Handling schnell zu Resultaten kommen wollen, sei Ihnen die Verwendung einer JavaScript-Bibliothek empfohlen. Die gängigen Frameworks nehmen Ihnen die Vereinheitlichung beim Event-Handling größtenteils ab.