JavaScript: Grundlagen der Ereignis-Verarbeitung

Ereignisbasierte Scripte

Das Kapitel Verarbeitung von Scripten hat Ihnen gezeigt, dass der Browser Scripte üblicherweise in dem Moment ausführt, in dem er den Code eines HTML-Dokuments herunterlädt, einliest und auf ein script-Element trifft.

Der Schicksal von JavaScript ist aber nicht, bloß in diesem kurzen Moment des Ladens des HTML-Dokuments ausgeführt zu werden und dann für immer zur Ruhe zu kommen. Die meisten JavaScripte sollen Interaktivität bieten. Der Schlüssel dazu ist, das haben wir bereits in den Grundkonzepten kennengelernt, die Überwachung und Behandlung von Ereignissen (englisch Event-Handling).

Moderne Scripte durchlaufen deshalb verschiedene Phasen:

Phase 1: Das Dokument wird empfangen und eingelesen

Dabei wird das JavaScript erstmals ausgeführt. Je nach Position des Scripts im Dokument hat das Script noch keinen vollständigen Zugriff auf das Dokument.

Objekte und Funktionen werden dabei definiert, sodass sie für die spätere Nutzung zur Verfügung stehen. Nicht alle notierten Funktionen werden dabei bereits aufgerufen.

Phase 2: Das Dokument ist fertig geladen

Der vollständige Zugriff auf das Dokument über das DOM ist jetzt möglich. Nun wird der Teil des Scripts aktiv, der dem Dokument JavaScript-Interaktivität hinzufügt. Das Script spricht vorhandene Elemente an und fügt ihnen Event-Handler hinzu (englisch handle = verarbeiten, handler = Verarbeiter). Das Script kann auch den Inhalt oder die Darstellung bestehender Elemente verändern und dem Dokument neue Elemente hinzufügen – auch DOM-Manipulation genannt.

Phase 3: Der Anwender bedient das Dokument und das Script reagiert darauf

Wenn die überwachten Ereignisse an den entsprechenden Elementen im Dokument eintreten, so werden Teile des Scripts aktiv. Die entsprechenden Event-Handler werden ausgeführt.

Resultierende Script-Struktur

Dieser Ablauf gibt die Struktur der meisten Scripte vor:

Wenn ein Script im Dokumentkopf eingebunden wird, so liegt zwischen Phase 1 und 2 eine große Zeitspanne. Dem Script ist der vollständige Zugriff auf das DOM noch nicht möglich. Es muss vielmehr warten, bevor es die Initialisierung ausführt.

Wenn ein Script am Ende des Dokuments eingebunden wird anstatt im Dokumentkopf, fallen Phase 1 und 2 ineinander. Denn es hat automatisch Zugriff auf alle HTML-Elemente, die davor im Dokument notiert sind. Die Initialisierung kann sofort gestartet werden. Es ist aus diesem und anderen Gründen ratsam, ein Script am Ende des Dokuments einzubinden.

Traditionelles Event-Handling

Um die Überwachung eines Ereignisses an einem Element zu starten, wird ein Event-Handler registriert. Im Folgenden wird die einfachste und älteste Methode vorgestellt, um Event-Handler zu registrieren.

In den Grundkonzepten haben wir die typischen Bestandteile der Ereignis-Überwachung kennengelernt:

Diese drei Bestandteile finden wir im Aufbau der JavaScript-Anweisung wieder. Das allgemeine Schema für das traditionelle Event-Handling lautet:

element.onevent = handlerfunktion;

Insgesamt hat die Anweisung die Form »Führe bei diesem Element beim Eintreten dieses Ereignisses diese Funktion aus.«

Der obige Pseudocode illustriert nur das allgemeine Schema. onevent ist lediglich ein Platzhalter für alle möglichen Eigenschaften, darunter onclick, onmouseover, onkeypress und so weiter.

Beispiel für traditionelles Event-Handling

Betrachten wir ein konkretes Beispiel. Dazu starten wir mit folgendem einfachen Dokument:

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Dokument mit JavaScript</title>
</head>
<body>

<button id="interaktiv">
  Dies ist ein Button ohne Bedeutung, aber mithilfe von JavaScript können wir ihn
  interaktiv gestalten. Klicken Sie diesen Button einfach mal mit der Maus an!
</button>

</body>
</html>

Dem button-Element mit der ID interaktiv soll nun ein Event-Handler zugewiesen werden. Ziel ist es, dass eine bestimmte Funktion aufgerufen wird, immer wenn der Anwender auf den Button klickt. Das Ereignis, das bei einem Mausklick ausgelöst wird, heißt click.

Wir werden nun in drei Schritte vorgehen:

  1. Ein Script am Ende des Dokuments hinzufügen
  2. Darin eine Funktion definieren
  3. Den Event-Handler registrieren um die Ereignis-Überwachung starten

Zunächst einmal platzieren wir ein Script am Ende des Dokuments. Dies ist die einfachste Weise um sicherzustellen, dass das Script Zugriff auf das gesamte Dokument hat. Vor </body> fügen wir also ein script-Element ein:

<script>
// …
</script>

Darin definieren wir die Funktion, die als Event-Handler dienen wird. Nennen wir sie clickHandler:

function clickHandler() {
  window.alert('Button wurde geklickt!');
}

Die einfache Beispiel-Funktion macht nichts anderes als ein Meldungsfenster auszugeben.

Schließlich starten wir die Ereignis-Überwachung. Der Code dazu lautet:

document.getElementById('interaktiv').onclick = clickHandler;

Was zunächst kompliziert aussieht, ist die Anwendung des bekannten Schemas element.onevent = handlerfunktion;.

Zum Registrieren des Event-Handler greifen wir über das DOM auf das Dokument zu. Dies ist direkt möglich, weil sich das Script am Ende des Dokuments befindet.

Der Code nutzt die DOM-Methode document.getElementById auf (zu deutsch: hole ein Element anhand der ID). Der Aufruf document.getElementById('interaktiv') gibt das Elementobjekt zurück, der den Button repräsentiert.

Damit der Zugriff auf das gewünschte Element einfach möglich ist, haben wir einen Angriffspunkt für das Script geschaffen: Das button-Element besitzt eine dokumentweit eindeutige ID namens interaktiv. Solche IDs werden neben Klassen (class-Attributen) benutzt, um Angriffspunkte für Stylesheets und Scripte zu bieten.

Nachdem wir das Element herausgesucht haben, registrieren wir den Event-Handler. Die Objekteigenschaft lautet onclick, denn es geht um das click-Ereignis. Dieser Name ist feststehend. Die Handler-Funktion lautet clickHandler. Dieser Name ist willkürlich gewählt, wir haben sie definiert.

Zusammengefasst sieht das Beispiel so aus:

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Beispiel für traditionelles Event-Handling</title>
</head>
<body>

<button id="interaktiv">
  Dies ist ein Button ohne Bedeutung, aber mithilfe von JavaScript können wir ihn
  interaktiv gestalten. Klicken Sie diesen Button einfach mal mit der Maus an!
</button>

<script>

function clickHandler() {
  window.alert('Button wurde geklickt!');
}

document.getElementById('interaktiv').onclick = clickHandler;

</script>
</body>
</html>

Den JavaScript-Code können wir später natürlich in eine externe Datei auslagern.

So einfach und nutzlos dieses Beispiel aussieht: Wenn Sie das Schema verstanden haben, beherrschen Sie einen Großteil der JavaScript-Programmierung und wissen, wie Scripte üblicherweise strukturiert werden und schließlich ausgeführt werden.

Funktionsweise des traditionellen Event-Handlings

Sie werden sich sicher fragen, wie Ereignis-Verarbeitung auf JavaScript-Ebene funktioniert. Dazu schauen wir uns den Aufbau der obigen Anweisung an:

document.getElementById('interaktiv').onclick = clickHandler;

Wir haben dort eine einfache Wertzuweisung (erkennbar durch das =). Auf der linken Seite steht eine Objekteigenschaft. Das Objekt ist der Elementknoten und die Eigenschaft heißt onclick. Auf der rechten Seite steht der zugewiesene Wert, clickHandler..

Nach dieser Zuweisung ist die Funktion clickHandler in der Objekteigenschaft gespeichert. Dies funktioniert, weil Funktionen in JavaScripte auch nur Objekte sind, auf die beliebig viele Variablen und Eigenschaften verweisen können.

Passiert nun ein Ereignis am Button-Element, so sucht der JavaScript-Interpreter nach einer Objekteigenschaft, die den Namen on gefolgt vom Ereignistyp trägt (im Beispiel onclick). Wenn diese Eigenschaft eine Funktion beinhaltet, führt er diese aus. Das ist erst einmal alles – aber enorm wichtig zum Verständnis des Event-Handlings.

Diese Methode hat verschiedene Nachteile und ist im Grunde überholt (siehe Nachteile und Alternativen). Dieses traditionelle Event-Handling ist aber immer noch verbreitet.

Event-Überwachung beenden

Wenn Sie einmal einen Event-Handler bei einem Element registriert haben, wird die Handler-Funktion künftig bei jedem Eintreten des Ereignisses ausgeführt – zumindest solange das Dokument im Browser dargestellt wird und es nicht neu geladen wird. Es ist möglich, die Event-Überwachung wieder mittels JavaScript zu beenden.

Wie beschrieben besteht das traditionelle Event-Handling schlicht darin, dass einer Objekteigenschaft (z.B. onclick) einen Wert zugewiesen wird. Um das Registrieren des Handlers rückgängig zu machen, schreiben wir erneut auf diese Objekteigenschaft. Allerdings weisen wir ihr keine Funktion zu, sondern einen anderen Wert. Dazu bietet sich beispielsweise der spezielle Wert null an, der soviel wie »absichtlich leer« bedeutet.

Das Schema zum Löschen des Event-Handlers lautet demnach:

element.onevent = null;

Wenn wir im obigen Beispiel die Überwachung des Klick-Ereignisses beim button-Element wieder beenden wollen, können wir notieren:

document.getElementById('interaktiv').onclick = null;

Häufiger Fehler: Handler-Funktion direkt aufrufen

Ein häufiger Fehler beim Registrierens eines Event-Handlers sieht folgendermaßen aus:

element.onevent = handlerfunktion(); // Fehler!

Oft steckt hinter dieser Schreibweise der Wunsch, der Handler-Funktion noch Parameter mitzugeben, damit diese darin verfügbar sind:

element.onevent = handlerfunktion(parameter); // Fehler!

Sie müssen sich die Funktionsweise des traditionellen Event-Handlings noch einmal durch den Kopf gehen lassen, um zu verstehen, warum diese Anweisung nicht den gewünschten Zweck erfüllt. Beim korrekten Schema element.onevent = handlerfunktion; wird ein Funktionsobjekt in einer Eigenschaft des Elementobjekts gespeichert.

Das ist beim obigen fehlerhaften Code nicht der Fall. Anstatt auf das Eintreten des Ereignisses zu warten, wird die Handler-Funktion sofort ausgeführt. Dafür verantwortlich sind die Klammern () hinter dem Funktionsnamen. Diese Klammern sind der JavaScript-Operator zum Aufruf von Funktionen.

Das Erste, was der JavaScript-Interpreter beim Verarbeiten dieser Zeile macht, ist die Funktion aufzurufen. Deren Rückgabewert wird schließlich in der onevent-Eigenschaft gespeichert. In den meisten Fällen hat die Handler-Funktion keinen Rückgabewert, was dem Wert undefined entspricht.

Wir wollen die Funktion aber nicht direkt aufrufen, sondern bloß das Funktionsobjekt ansprechen, um es in der Eigenschaft zu speichern. Daher dürfen an dieser Stelle keine Klammern hinter dem Namen notiert werden.

Der Wunsch, der Handler-Funktion gewisse Daten als Parameter zu übergeben, ist verständlich. Die obige fehlerhafte Schreibweise vermag dies aber nicht zu leisten. Das bekannte Schema element.onevent = handlerfunktion; muss eingehalten werden.

Eine Lösungsmöglichkeit ist, den Funktionsaufruf, der die Parameter übergibt, in einer zusätzlichen Funktion unterzubringen. Schematisch:

function helferfunktion(parameter) {
   // Arbeite mit dem Parameter und verarbeite das Ereignis
}
function handlerfunktion() {
   helferfunktion('Parameter');
}
element.onevent = handlerfunktion;

Das Beispiel mit dem Button können wir so anpassen, dass in der Handler-Funktion bloß eine andere Hilfsfunktion mit Parametern ausgeführt wird:

function ausgabe(text) {
  window.alert(text);
}

function clickHandler() {
  ausgabe('Huhu!');
}

document.getElementById('interaktiv').onclick = clickHandler;

In der Handler-Funktion clickHandler wird die neue Funktion ausgabe mit Parametern aufgerufen. Diese wurde verallgemeinert und ist wiederverwendbar: Sie nimmt einen Parameter an, einen String, und gibt diesen in einem Meldungsfenster aus.

Eingebettete Event-Handler-Attribute

Wir haben kennengelernt, wie wir externe JavaScripte einbinden und darin auf traditionelle Weise Event-Handler registrieren können. Der Vorteil davon ist, dass wir HTML- und JavaScript-Code und damit das Dokument und das JavaScript-Verhalten trennen können.

Wann immer es möglich ist, sollten Sie diese Vorgehensweise des Unobtrusive JavaScript wählen. Es soll aber nicht verschwiegen werden, dass es auch möglich ist, JavaScript direkt im HTML-Code unterzubringen und damit auf Ereignisse zu reagieren.

Zu diesem Zweck besitzen fast alle HTML-Elemente Attribute, in die Sie den auszuführenden JavaScript-Code direkt hineinschreiben können. In diesem Code können Sie natürlich auch eigene Funktionen aufrufen, die sie in einem script-Element oder einer externen JavaScript-Datei definiert haben. Die Attribute sind genauso benannt wie die entsprechenden JavaScript-Eigenschaften: Die Vorsilbe on gefolgt vom Ereignistyp (z.B. click). Das Schema lautet dementsprechend:

<element onevent="JavaScript-Anweisungen">

Ein konkretes Beispiel:

<button onclick="window.alert('Button wurde geklickt!');">
  Klicken Sie diesen Button an!
</button>

Hier enthält das Attribut die JavaScript-Anweisung window.alert('Button wurde geklickt!');, also einen Aufruf der Funktion window.alert. Sie können mehrere Anweisungen in einer Zeile notieren, indem Sie sie wie üblich mit einem Semikolon trennen. Zum Beispiel Funktionsaufrufe:

<button onclick="funktion1(); funktion2();">
  Klicken Sie diesen Button an!
</button>

Diese Attribute, die JavaScript enthalten, werden auch Inline-Event-Handler genannt.

Die Verwendung solcher Attribute bringt viele Eigenheiten und Nachteile mit sich, auf die an dieser Stelle nicht weiter eingegangen wird. Es gibt viele gute Gründe, HTML und JavaScript möglichst zu trennen und auf solches Inline-JavaScript zu verzichten. Zum Einstieg sollten sie sich mit dem Registrieren von Event-Handlern mittels JavaScript vertraut machen, wie es in den vorigen Abschnitten erläutert wurde.

Häufiger Fehler: Auszuführenden Code als String zuweisen

Nachdem wir Inline-JavaScript angeschnitten haben, sei auf einen weiteren häufigen Fehler beim traditionellen Event-Handling hingewiesen. Manche übertragen ihr Wissen über Inline-Event-Handler aus HTML auf das das Registrieren von Event-Handlern in JavaScript. Sie versuchen z.B. folgendes:

element.onclick = "window.alert('Element wurde geklickt!');"

Oder gleichwertig mithilfe der DOM-Methode setAttribute:

element.setAttribute("onclick", "window.alert('Element wurde geklickt!');");

Sprich, sie behandeln die Eigenschaft onclick und dergleichen wie Attribute unter vielen. Für die meisten anderen Attribute gilt das auch. Ein Beispiel:

<p>
  <a
    id="link"
    href="http://de.selfhtml.org/"
    title="Deutschsprachige Anleitung zum Erstellen von Webseiten">
    SELFHTML
  </a>
</p>

<script>
var element = document.getElementById("link");
element.title = "Die freie Enzyklopädie";
element.href = "http://de.wikipedia.org/";
element.firstChild.nodeValue = "Wikipedia";
</script>

Das Script spricht ein Link-Element über seine ID an und ändert dessen Attribute title und href sowie schließlich dessen Textinhalt. Das Beispiel illustriert, dass sich die Zuweisungen der Attributwerte im HTML und im JavaScript stark ähneln. Die neuen Attributwerte werden im JavaScript einfach als Strings notiert.

Diese Vorgehensweise ist beim Setzen von Event-Handler-Attributen über JavaScript nicht ganz falsch. Theoretisch haben folgende Schreibweisen denselben Effekt:

// Methode 1: Traditionelles Event-Handling
function handlerfunktion() {
   window.alert("Hallo Welt!");
}
element.onevent = handlerfunktion;

// Methode 2: Auszuführenden Code als String zuweisen
// (Achtung, nicht browserübergreifend!)
element.setAttribute("onevent", "window.alert('Hallo Welt!');");

Ihnen mag die die zweite Schreibweise in vielen Fällen einfacher und kürzer erscheinen. Doch zum einen hat sie das Problem, dass sie in der Praxis längst nicht so etabliert ist wie die traditionelle. Ältere Browser unterstützen diese Schreibweise nicht.

Davon abgesehen hat es Nachteile, JavaScript-Code nicht in Funktionen zu ordnen, sondern in Strings zu verpacken. Der Code wird unübersichtlicher und Fehler sind schwieriger zu finden. Sie sollten daher das traditionelle Schema bevorzugen.