JavaScript/DOM/EventTarget/addEventListener

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Die Methode EventTarget.addEventListener() ermöglicht es, einen oder mehrere Event-Handler für ein bestimmtes Event zu einem Objekt hinzuzufügen, das die EventTarget-Schnittstelle unterstützt. Dazu gehören beispielsweise die HTML-Elemente.

Details: caniuse.com

Syntax target.addEventListener(type, listener[, useCapture]);

  • type: String mit dem Ereignistyp
  • listener: Eine Handler-Funktion oder ein EventListener-Objekt
  • useCapture: optionaler Boolean-Wert für die Event-Phase.

Die heute übliche Vorgehensweise ist, als listener eine Funktion zu übergeben. Sie wird bei der Verarbeitung des Events vom Browser aufgerufen. Als Aufrufkontext, also als Inhalt von this, findet sie das EventTarget vor, auf dem der Eventhandler registriert wurde, und als Argument erhält sie ein Event-Objekt übergeben, das weitere Informationen zum aktuellen Ereignis enthält.

Alternativ können Sie ein Objekt mit EventListener-Schnittstelle übergeben. Dazu muss dieses Objekt eine Methode namens handleEvent besitzen, die der Browser zur Eventbehandlung aufruft. Diese Technik stammt aus der DOM Level 2 Spezifikation (2003) und ist nur noch vorhanden, damit alter Code weiter funktioniert, aber sie hat die Besonderheit, dass die handleEvent-Methode ihr eigenes Objekt als this vorfindet.

Anwendungsbeispiel

Beispiel ansehen …
window.addEventListener('DOMContentLoaded', function () {
    const addierer = document.querySelector('button[name=addiere]');
    addierer.addEventListener('click', handleAddiereKlick);

    function handleAddiereKlick() {
        const wert1 = document.getElementById("wert1").valueAsNumber;
        const wert2 = document.getElementById("wert2").valueAsNumber;
        document.getElementById('summe').textContent = wert1 + wert2;
    }
}

Das HTML zum Beispiel enthält in einem fieldset zwei input-Elemente für Zahlen (type="number") und einen Addiere-Button. Im HTML findet sich kein onclick Attribut, der Button ist aus der Sicht des HTML Teils also funktionslos. Das wird im JavaScript-Teil mit Hilfe von addEventListener geändert, das auf dem Additions-Button für das Ereignis click die Funktion handleAddiereKlick registriert.

Diese Vorgehensweise nennt sich Unobtrusive JavaScript (unaufdringliches JavaScript). Das Prinzip ist, dass sich HTML nur darum kümmert, was angezeigt wird und welche Bedeutung es hat. Wie es aussieht, obliegt CSS, und wie es auf den Benutzer reagiert, obliegt dem Browser und den Scripten.

Das Beispielscript sucht mittels querySelector den Button mit name="addiere" heraus und registriert für das Event click die Funktion handleAddiereKlick als Event-Handler.

Sie sehen aber zuvor noch einen anderen Aufruf von addEventListener, und zwar auf dem window-Objekt. Das Event, für das ein Event-Handler registriert wird, heißt DOMContentLoaded, und der Event-Handler ist eine anonyme Funktion, die das eigentliche Beispiel enthält. Dies ist dem Umstand geschuldet, dass das Selfhtml Frickl, das Sie mittels "ausprobieren" aufrufen können, JavaScript immer im <head>-Element des HTML-Dokuments einstellt. Das Script wird damit zu einem Zeitpunkt ausgeführt, wo der Browser den Addiere-Button noch gar nicht kennt, und der Aufruf von querySelector würde nichts finden. Das DOMContentLoaded-Event wird vom Browser ausgelöst, sobald er den HTML-Text vollständig verarbeitet hat. Wenn wir den Beispielcode in einem Handler für dieses Event ausführen, können wir sicher sein, dass das DOM vollständig ist.

Die handleAddiererKlick-Funktion ist unspektakulär. Sie sucht sich an Hand der ID die beiden Eingabefelder aus dem DOM (getElementById) und bekommt dafür jeweils ein HTMLInputElement-Objekt vom Browser. Darin findet sich eine Eigenschaft namens valueAsNumber, die den eingegebenen Zahlenwert enthält. Diese beiden Werte addiert sie auf, und schreibt sie in die Eigenschaft textContent, mit der man den Textinhalt eines HTML-Elements setzen kann.

Parameter übergeben und den Wert von this beeinflussen

Der zweite Parameter von addEventListener ist eine Referenz auf den aufzurufenden Eventhandler. Dessen Aufruf wird vom Browser durchgeführt und erfolgt nach seinen Regeln. Insbesondere haben Sie keinen Einfluss darauf, welche Argumente die Eventhandler übergeben bekommt, es ist immer das Event-Objekt. Der Wert für den Aufrufkontext in this wird ebenfalls vom Browser festgelegt.

Wenn das nicht zu Ihrem Anwendungsfall passt, haben Sie unterschiedliche Möglichkeiten. Wenn Sie dem Eventhandler weitere Argumente übergeben müssen, können Sie für das Event eine anonyme Funktion registrieren, die als Adapter dient und den Aufruf des Browsers in den von Ihnen gewünschten Aufruf übersetzt. Genauso gut können Sie auch eine Pfeilfunktion einsetzen.

Anpassen des Eventhandler-Aufrufs
button1.addEventListener('click', function(event) {
   handleClick(event, 'Es klickte mich!');
});
button2.addEventListener('click', event => handleClick(event, 'Es klickte mich!'));

function handleClick(event, message) {
    alert(message);
}

Der Einsatz einer Pfeilfunktion ist ab ECMAScript 2015 möglich, also überall außer im Internet Explorer, und stellt die übersichtlichste Art dar, das Problem zu lösen.

Mit einer Adapterfunktion können Sie auch das Problem lösen, dass Sie nicht ohne weiteres eine Objektmethode als Eventhandler registrieren können. Sie würde nicht ihr eigenes Objekt als this vorfinden, sondern das currentTarget des Event-Objekts.

Objektmethode als Eventhandler
class ClickMessenger {
   construct(button, message) {
      this.message = message;
      button.addEventListener('click', event => this.handleClick);
   }
   handleClick(event) {
      alert(this.message);
   }
}
new ClickMessage(button3);

In diesem Beispiel wird eine Klasse ClickMessenger erzeugt, deren Konstruktor ein DOM Element und eine Nachricht erwartet. Im Konstruktor wird ein click-Handler für dieses DOM Element registriert, der eine Pfeilfunktion nutzt, um den Eventhandler-Aufruf des Browsers abzufangen und mit dem richtigen this an eine Methode des Objekts zu delegieren. An dieser Stelle ist eine Besonderheit von Pfeilfunktionen wesentlich: sie bekommen kein eigenes this Objekt. Das this innerhalb der Pfeilfunktion ist damit das gleiche this wie in der Konstruktorfunktion: das ClickMessenger-Objekt.

Beachten Sie: Wenn Sie eine normale Funktion statt einer Pfeilfunktion verwenden, haben Sie innerhalb dieser Funktion wieder das this-Problem. Um es zu lösen, müssten Sie dort, wo Sie den Handler registrieren, eine Variable anlegen, in der Sie this speichern, und an Stelle von this in der Handler-Funktion diese Variable nutzen (warum das geht, ist unter Closures erklärt.

Eine andere Möglichkeit ist die Verwendung der bind-Methode, die für jede Funktion von Function.prototype bereitgestellt wird:

Objektmethode als Eventhandler
class SomeClass {
   someHandler(event) { ... }
   someExtraHandler(data, event) { ... }
}
let obj = new SomeClass();
elem1.addEventListener('click', obj.someHandler.bind(obj));
elem2.addEventListener('click', obj.someExtraHandler.bind(obj, 'data'));

Im Prinzip bewirkt bind nichts anderes als die Erzeugung einer kleinen Adapterfunktion, die dafür sorgt, dass die gebundene Funktion mit dem gewünschten this aufgerufen wird. Man kann sogar dafür sorgen, dass der Funktion Extraparameter übergeben werden, das müssen dann aber Parameter sein, die zum bind-Zeitpunkt bekannt sind, und es ist eine Eigentümlichkeit von bind, diese Extraparameter als erstes zu übergeben. Daraus ist die "verdrehte" Parameterliste von someExtraHandler entstanden.

Eine weitere Alternative ist ein Objekt mit EventListener-Schnittstelle. Wenn Sie an Stelle einer Funktion ein solches Objekt registrieren, wird zur Eventbehandlung dessen handleEvent-Methode aufgerufen, und beim Aufruf wird this auf das registrierte Objekt gesetzt. Sie können dieses Objekt verwenden, um Daten für die Ereignisbehandlung zur Verfügung zu stellen, oder auch, um sich ein Objekt zu merken, an das Sie die Ereignisbehandlung delegieren möchten.

Beispiel
elem.addEventListener('click', { handleEvent: foo, message: 'bar' });

function foo(event) {
    alert(this.message);
}, true);

Eine solche Vorgehensweise ist auch dann nützlich, wenn Sie objektorientiert programmieren und Eventhandler als Methode eines Objekts implementieren wollen. Sie können das EventListener-Objekt als Brücke verwenden, um Ihre Eventhandler-Methode mit dem richtigen Wert für this aufzurufen.

Zugriff auf das DOM Element, für das das Event ausgelöst wurde

Das Event-Objekt, das Ihr Eventhandler als Argument übergeben bekommt, besitzt zwei Eigenschaften, die auf die beteiligten Elemente verweisen: currentTarget und target.

Die currentTarget-Eigenschaft enthält das Objekt, für das der Eventhandler registriert wurde. Wenn Sie beispielsweise auf einem <form>-Element einen click-Handler registrieren, gelangen dorthin alle click-Events, die innerhalb dieses Forms ausgelöst wurden (Bubbling). Beim Aufruf dieses Handlers ist das currentTarget immer das Form.

Die target-Eigenschaft verweist dagegen auf das Objekt, auf dem das Event ausgelöst wurde. Um bei dem Beispiel aus dem vorigen Absatz zu bleiben - klicken Sie in diesem Form einen Button, so verweist die target-Eigenschaft auf diesen Button (oder auf ein Kind-Element davon, falls Sie im Button-Inhalt HTML verwenden!).

Wenn Sie eine Funktion als Eventhandler übergeben, können Sie auch das this-Objekt verwenden. Sein Inhalt entspricht dann der currentTarget-Eigenschaft des Eventobjekts. Bei einem EventListener-Objekt geht das nicht, dort entspricht this immer dem EventListener-Objekt.

Empfehlung: Verwenden Sie event.target und event.currentTarget, um auf die am Event beteiligten DOM Elemente zuzugreifen, und benutzen Sie this nur in Methoden von Objekten. Damit ist unmissverständlich klar, was Sie an dieser Stelle tun.

Das folgende Beispiel registriert einen Eventhandler für das change-Event eines select-Elements und zeigt bei Änderungen den neu ausgewählten Wert in der Konsole an.

Beispiel
selectElem.addEventListener('change', getSelection, false);

function getSelection(event) {
    var value = event.target.options[this.selectedIndex].value;
    console.log("Selektiert: " + value);
}

UseCapture / EventListenerOptions

Mit dem dritten Parameter der addEventListener-Funktion bestimmen Sie, in welcher Phase der Ereignisbehandlung der Handler aufgerufen wird.

  • false: (Standardwert) der Aufruf findet in der Target- oder Bubbling-Phase statt. Vorsicht - manche Events haben gar keine (blur,focus) oder eine verkürzte Bubbling-Phase (z.B. gelangt 'load' nicht über das Dokument hinaus weiter zum Window).
  • true: Der Aufruf findet in der Capturing-Phase statt.

Eine Entwicklung von 2016 ist die Umwandlung von UseCapture zu einem Options-Parameter. An Stelle des boolean Wertes kann ein Objekt übergeben werden, das mehrere Boolean-Parameter gruppiert. Wird einer dieser Parameter nicht angegeben, wird er als false angenommen.

  • capture: steuert, ob der EventHandler in der Capture- oder Target/Bubble-Phase gerufen wird (entspricht dem alten UseCapture-Parameter).
  • passive: Der EventHandler ist passiv und kann das Ereignis nicht abbrechen (preventDefault wird ignoriert). Dies ermöglicht dem Browser gewisse Optimierungen für eine glattere UI-Darstellung
  • once: Der EventHandler wird nach seinem ersten Aufruf automatisch wieder entfernt.

Problematisch daran ist, dass man nicht einfach { passive:true } an einem Browser übergeben kann, der das nicht unterstützt. Er würde es als true interpretieren und damit den Eventhandler in die Capturing-Phase verlegen. Ob ein Browser diese Erweiterung unterstützt, lässt sich durch eine etwas obskure Technik detektieren. Man nutzt dafür aus, dass sich Objekteigenschaften durch getter-Methoden realisieren lassen. Die nachfolgend gezeigte Funktion zeigt eine Feature Detection, die ermittelt, ob der Browser Option-Objekte unterstützt. Das Ergebnis wird in einem Property der Funktion gespeichert, so dass die Ermittlung nur einmal erfolgen muss.

Feature Detection für Option-Objekte in addEventListener
function supportsEventListenerOptions() {
  if (!supportsEventListenerOptions.hasOwnProperty("result")) {
    supportsEventListenerOptions.result = false;
    const options = {
      get capture() {
        supportsEventListenerOptions.result = true;
        return false;
      };
    document.createElement("div").addEventListener("test", function() {}, options);
  }
  return supportsEventListenerOptions.result;
}

Die Funktion prüft zunächst, ob sie bereits eine result-Eigenschaft besitzt. Wenn ja, wird die Feature-Detection übersprungen. Für die Feature Detection wird das result-Property zunächst mit false initialisiert und dann auf einem temporär erzeugten div-Element ein Dummy-Event registriert. Für diese Registrierung wird ein Optionsobjekt verwendet, das an Stelle einer einfachen Eigenschaft eine getter-Methode für die capture-Eigenschaft besitzt. Ein aktueller Browser wird im Verlauf dieser Registrierung den Wert von capture auslesen und dadurch die getter-Methode aufrufen. Diese setzt das result-Property der Funktion auf true. Das erzeugte div-Element mit seinem Eventhandler wird danach nicht weiter verwendet und von JavaScript aus dem Speicher entfernt.

Zum Abschluss wird die result-Eigenschaft der Detektorfunktion an den Aufrufer zurückgegeben.

Mehrere Eventhandler einfügen

Einer der Vorteile dieser Methode gegenüber den klassischen Eventhandler-Attributen ist, dass Sie mehrere Event-Handler hinzufügen können.

Beispiel
button.onclick = LadeIrgendwas;
button.onclick = LadeNochwas;
Hier wird der erste Handler durch den zweiten überschrieben und deshalb nicht ausgeführt.
Beispiel
button.addEventListener ('click', LadeIrgendwas);
button.addEventListener ('click', LadeNochwas);
Beide Funktionen werden nacheinander ausgeführt, sobald der Button geklickt wird.

Es war einmal: attachEvent

Der Internet Explorer 6-8 implementierte addEventListener noch nicht. Statt dessen gab es eine eigene Methode namens attachEvent(), um Eventhandler hinzuzufügen. Mit detachEvent() können diese wieder entfernt werden.

Diese Browser sind heute (2021) vollkommen obsolet. Selbst der Interner Explorer 11 wurde von Microsoft so gepatcht, dass er sein baldiges Ableben ankündigt. Sie müssen deshalb keine Logik mehr programmieren, die bei einer fehlenden addEventListener-Methode probiert, ob es attachEvent gibt. Die früher hier dargestellte Helferfunktion wurde deshalb depubliziert.

Falls Sie mit Alt-Code dieser Art konfrontiert sind: attachEvent und detachEvent verhalten sich beinahe so wie addEventListener und removeEventListener. Der einzige Unterschied ist, dass die alten IE-Methoden noch ein "on" vor dem Ereignisnamen erwarteten, vermutlich als Analogie zu den on...-Attributen, mit denen man Events im HTML registrieren kann.

Siehe auch

Weblinks