JavaScript: Fokus-Ereignisse zentral behandeln und aktives Element finden

· Keine Kommentare

»Wie kann ich herausbekommen, welches Element gerade den Fokus hat (oder zuletzt den Fokus hatte)?«

Diese Frage bezieht sich üblicherweise auf Elemente, die standardmäßig fokussierbar sind. Das sind vor allem Links und Formularfelder.

document.activeElement (Microsoft / HTML5)

Eine sehr einfache Lösung, die leider noch nicht von allen Browser unterstützt wird, ist die Objekteigenschaft document.activeElement. Diese enthält das Elementobjekt, das gerade den Fokus hat. Wenn kein besonderes Element im Dokument den Fokus hat, dann zeigt activeElement schlicht auf das body-Element.

Diese Eigenschaft hat ursprünglich Microsoft erfunden und dokumentiert sie im Microsoft Developer Network: activeElement Property. Sie wird aber auch von Opera ab Version 7.2 und Firefox ab Version 3 unterstützt. Im Zuge von HTML5 soll activeElement standardisiert werden: Document-level focus APIs werden.

Nun ist diese Eigenschaft nicht Event-basiert, was der Funktionsweise vieler JavaScripte widerspricht, und sie wird noch nicht von allen Browsern umgesetzt. Das führt uns zu der Frage:

Wie kann ich alle Fokus-Ereignisse im Dokument überwachen und behandeln?

Eine Möglichkeit wäre, bei allen fraglichen fokussierbaren Elementen das focus-Event zu überwachen. Auf diese Weise lässt sich document.activeElement für die Browser nachbauen, die die Eigenschaft nicht unterstützen. Dazu könnte man allen Elementen, die im Dokument existieren, nach dem vollständigen Laden des Dokuments focus-Handler vergeben. JavaScript-Frameworks vereinfachen so ein Vorgehen, z.B. würde man in jQuery einfach $("a, area, input, select, textarea, button").focus(handlerfunktion) notieren und schon hätten die wichtigsten fokussierbaren Elemente einen entsprechenden Event-Handler.

Viel eleganter als das Registrieren von dutzenden focus-Handlern direkt bei den fokussierbaren Elementen wäre eine zentrale Überwachung (Event Delegation). Allerdings gehört das focus-Event ebenso wie das blur-Event zu denjenigen Ereignissen, die nicht im DOM-Elementbaum aufsteigen (Bubbling). Das macht eine zentrale Überwachung von allen focus- oder blur-Ereignissen, die aus verschiedenen Elementen im Dokument stammen, zumindest mithilfe des Bubblings unmöglich.

Event Capturing von focus-Events (W3C DOM Events)

Damit sind wir aber noch noch am Ende angelangt. Im traditionellen Event-Handling gibt es nur Bubbling (Aufsteigen), im fortschrittlicheren Modell des W3C-Standards DOM Events gibt es zusätzlich die Capturing-Phase, für die ebenfalls Event-Handler registriert werden. Kurz gesagt: Ein Event startet ganz oben im Elementbaum und bewegt sich in der Hierarchie abwärts zum Zielelement. Auf diesem Weg werden aller Handler gefeuert, die für die Capturing-Phase registriert sind. Nachdem die Handler beim Zielelement ausgelöst wurden, beginnt das bekannte Aufsteigen (Bubbling).

Mithilfe des Event Capturings können an einem Element weit oben in der Hierarchie alle focus-Ereignisse seiner Kindelemente verarbeitet werden, auch wenn diese nicht aufsteigen (bubblen).

document.addEventListener("focus", function (e) {
	alert("Es ist ein focus-Event bei diesem Element passiert: " + e.target);
}, true);

Entscheidend beim Aufruf von addEventListener ist hier der dritte Parameter true, der für die Capturing-Phase steht. Dasselbe Schema ist natürlich auch auf blur-Ereignisse anwendbar.

Cross-Browser document.activeElement

Für Browser, die activeElement nicht unterstützen, jedoch DOM Events, können wir activeElement mittels Event Capturing nachbauen:

if (typeof document.activeElement == "undefined" && document.addEventListener) {
	document.addEventListener("focus", function (e) {
		document.activeElement = e.target;
	}, true);
}

Auf diese Weise steht uns die Eigenschaft document.activeElement zur Verfügung, sobald ein focus-Ereignis im Dokument passierte. Damit haben wir die Browserunterstützung von activeElement verbreitet und eine Möglichkeit, alle focus-Ereignisse zentral zu verarbeiten.

Was noch fehlt ist Event Delegation im Internet Explorer. Dazu möchte ich zwei weitere Ansätze vorstellen.

DOMFocusIn (W3C DOM Events)

Es hat zwar seinen Sinn, dass das focus-Event nicht aufsteigt, aber das W3C hat trotz Event Capturing von focus und blur erkannt, dass Webautoren sich in manchen Fällen wünschen, dass der Fokus eines Elements einen Event auslöst, der aufsteigt. Den gibt es auch, nämlich DOMFocusIn. Dieser Event tritt immer ein, wenn auch ein focus-Event passiert.

Im Prinzip könnten wir also auch schreiben:

document.addEventListener("DOMFocusIn", function (e) {
	if (e.target != document) {
		alert("Es ist ein DOMFocusIn-Event bei diesem Element passiert: " + e.target);
	}
}, false);

Hier steht der dritte Parameter auf false, das heißt, wir registrieren den Handler für die Bubbling-Phase. Das Pendant zu DOMFocusIn ist DOMFocusOut, das sich analog zum blur-Event verhält, jedoch ebenfalls aufsteigt.

Leider wird dieses Ereignis bisher nur von den Webkit-Browsern (Safari, Chrome) sowie Opera unterstützt. Und in diesen ist es nicht unbedingt nötig, da sie auch Event Capturing für focus hinreichend unterstützen.

Notwendig ist jedoch eine Alternativlösung für den Internet Explorer. Denn der unterstützt in der aktuellen Version 8 den DOM-Events-Standard nicht und kennt daher die Methode document.addEventListener() nicht. Das Microsoft-eigene Event-Handling-Modell definiert document.attachEvent, welches jedoch nur Bubbling beherrscht und damit für diese Zwecke unbrauchbar ist.

Die Events activate bzw. focusin (Microsoft)

Der Internet Explorer kennt dafür die Ereignisse focusin und focusout. Peter-Paul Koch dokumentiert diese und beschreibt sie als äquivalent zu den Ereignissen DOMFocusIn und DOMFocusOut im W3C-DOM. Außerdem entsprechen sie weitesgehend zwei weiteren Microsoft-eigenen Ereignistypen, nämlich activate und deactivate.

Im Gegensatz zu focus und blur steigen diese Events allesamt auf (sie »bubblen«). Wir können sie also beim traditionellen Event-Handling für Event Delegation einsetzen. Beispiel für den Internet Explorer:

document.onfocusin = function () {
	var src = window.event.srcElement;
	if (src != document.documentElement && src != document.body) {
		alert("Element wurde fokussiert: " + src.tagName);
	}
};

Somit hätten wir alle Browser abgedeckt.

Beliebige Elemente fokussierbar machen: tabindex="0" und tabindex="-1"

HTML5 standardisiert eine weitere Microsoft-Erfindung, die bereits in WAI-ARIA aufgenommen wurde: Das ursprünglich traditionell fokussierbare Elemente (Formularfelder, Links usw.) gedachte HTML-Attribut tabindex ist nun für alle Elemente zugelassen. Indem man tabindex="0" vergibt, ist das Element fokussierbar, per Tastatur anspringbar und aktivierbar wie etwa ein Hyperlink oder Button. Alle großen Browser unterstützen diese Bedeutung von tabindex schon länger.

Dies ist äußerst praktisch, um gewöhnliche, standardmäßig nicht fokussierbare Elemente, die mit JavaScript-Interaktivität versehen oder gar zu einem komplexen »Widget« umgewandelt wurden, für die Tastaturbedienung zugänglich zu machen. Ein tolles Beispiel ist das Script jQuery Accessible Tabs - Wie man Tabs WIRKLICH zugänglich macht.

Der Clou aus JavaScript-Sicht ist, dass Elemente mit tabindex="0" genauso die Ereignisse focus/blur, DOMFocusIn/DOMFocusOut bzw. focusin/focusout feuern und die Elementobjekte sogar die Methoden focus() und blur() anbieten, um den Fokus mittels JavaScript auf sie zu setzen.

Hintergrundlektüre:
JavaScript: Einbindung in HTML und Ereignisverarbeitung
Einführung in WAI-ARIA

« Nachtrag zum Ada-Lovelace-Tag 2009

Los gehts »

Kommentar verfassen

Der Kommentar erscheint nicht sofort unter dem Artikel, sondern wandert erst einmal in die Moderationsschleife. Er wird dann zeitnah freigeschaltet.