Erhältlich u.a. bei Amazon.de
11
Von Wolfgang Schwarz
Event-Handler sind die Sinnesorgane einer Webseite. Sie teilen ihr mit, was um sie herum vorgeht, so dass sie darauf mehr oder weniger sinnvoll reagieren kann: Wurde die Maus bewegt? ein Element angeklickt? eine Taste auf der Tastatur gedrückt? Ist ein Bild fertig geladen? oder ein JavaScript-Fehler aufgetreten? Alles das sind Ereignisse (Events), die man mit JavaScript abfangen kann.
Bis vor einigen Jahren konnten HTML-Elemente höchstens auf eine Handvoll verschiedener Events reagieren. Inzwischen erkennt beim Internet Explorer 5 allein das Image-Objekt 49 verschiedene Event-Typen. Eine Liste aller pro Element verfügbaren Ereignisse werden Sie, schon aus Platzgründen, hier nicht finden. Stattdessen geht es in diesem Kapitel um die verschiedenen Möglichkeiten, Events abzufangen und auf sie zu reagieren. Auch diese Möglichkeiten haben nämlich seit Netscape 2 erheblich zugenommen - und, wie Sie wahrscheinlich schon vermuten: Netscape und Microsoft haben sich wieder zwei recht unterschiedliche Modelle einfallen lassen.
Event-Handler
Um ein auf ein Element eintreffendes Ereignis abzufangen, müssen Sie dem Element einen Event-Handler zuweisen. Die bekannteste Möglichkeit dazu ist, den Handler direkt als Attribut in den HTML-Tag zu setzen:
<a href="gehweg.html" onClick="alert('Auf Wiedersehen!')"> ein Link </a>
Das Handler-Attribut (hier onClick) hat als Wert einen String, in dem beliebige, durch Semikolons abgetrennte JavaScript-Anweisungen stehen dürfen.
Seit Netscape 3 und Internet Explorer 4 können Event-Handler auch als Eigenschaft des entsprechenden JavaScript-Objekts angsprochen werden:
zielObjekt.eventhandler = anweisung;
Also zum Beispiel:
function sagGutenTag() { alert("Guten Tag!"); }
window.onload = sagGutenTag;
Wie Sie sehen, wird dem Event-Handler eine Referenz auf die gewünschte JavaScript-Anweisung zugewiesen, nicht wie bei HTML-Attributen ein String, der die Anweisung selbst enthält. Eine Funktions-Referenz erhalten Sie, indem Sie einfach den Funktionsnamen ohne Klammern angeben.
Beachten Sie auch, dass die Event-Handler-Eigenschaften (onmouseover etc.) in JavaScript stets ganz in Kleinbuchstaben geschrieben werden!
Das World Wide Web Consortium (W3C) führte mit Level 2 seiner DOM-Spezifikation eine weitere Möglichkeit ein - zur Drucklegung dieses Buches wird diese jedoch nur von Netscape 6 unterstützt:
Element.addEventListener("eventTyp", anweisung, capture)
Das vorige Beispiel sähe damit so aus:
window.addEventListener("load", sagGutenTag, false);
Der erste Parameter enthält den Namen des Event-Typs ( nicht etwa den des Handlers, also "load" und nicht "onload". Der dritte Parameter ist ein Boolean-Wert. Um zu verstehen, wozu er da ist, müssen wir uns ein wenig mit dem Lebenslauf eines Events beschäftigen.
Betrachten wir einen Ausschnitt aus einem einfachen HTML-Dokument:
<body> <div id="einDiv"> <a id="einLink" href="weg.htm"> ein Link </a> </div> </body>
Angenommen, Ihr Besucher klickt mit der Maus auf den Link und erzeugt dadurch einen Klick-Event. In älteren Browsern wird dieser direkt an sein Ziel-Element, den Link, weitergeleitet, wo sich entweder ein onclick-Handler seiner annimmt oder der Event ungenutzt verschwindet (und die im Link angegebene Adresse angesprungen wird).
Event-Weg bei Netscape 4
Bei Netscape 4 hingegen sickert ein Event von oben nach unten durch die Objekt-Hierarchie, bis er schließlich bei seinem Ziel ankommt. Das heißt, auf dem Weg zum angeklickten Link trifft Ihr Klick zuerst auf das window-Objekt, dann auf window.document und erst zuletzt auf den Link. Wenn das Element einDiv mit CSS-Angaben positioniert wurde, kommt der Event außerdem noch an window.document.einDiv und window.document.einDiv.document vorbei. (Positionierte div-Elemente werden von Netscape 4 als Layer interpretiert, und Layer haben ein eigenes document-Objekt, vgl. Kapitel 9).
An jeder Stelle auf seinem Weg können Sie den Klick abfangen. Dazu dient die Methode captureEvents(). Die folgende Anweisung fängt beispielsweise alle Klick-Ereignisse auf document-Ebene ab:
window.document.captureEvents(Event.CLICK); window.document.onclick = klickHandler;
Der an captureEvents() übergebene Parameter bestimmt, welcher Event-Typ abgefangen wird. Diese Event-Typen sind festgelegt als statische Eigenschaften des Objekts window.Event und werden ganz in Großbuchstaben geschrieben. Um mehrere Events abzufangen, trennen Sie diese mit dem Zeichen für bitweises Oder, "|":
window.captureEvents(Event.MOUSEOVER | Event.MOUSEOUT);
Abbildung 11.1: Event-Weg im Beispieldokument bei Netscape 4
Event-Weg bei Microsoft
Beim Internet Explorer (ab Version 4) bewegen sich die Events genau in der umgekehrten Richtung: Der Klick-Event trifft zuerst auf den Link und blubbert von dort durch die Element-Hierarchie nach oben. Dieses Konzept nennt sich event bubbling. Unterwegs wird bei jedem Element, für das ein onclick-Handler registriert ist, dieser Handler aufgerufen ( eine zusätzliche Anweisung wie captureEvents() ist nicht nötig. Im folgenden Beispiel löst ein Klick auf den Link deshalb beide alerts aus:
<div onClick="alert('Klick auf Div')"> <a href="#" onClick="alert('Klick auf Link')">ein Link</a> </div>
An diesem Beispiel sehen Sie auch, dass Events beim Internet Explorer nicht nur eine andere Richtung, sondern sogar einen anderen Weg nehmen als bei Netscape 4: Vom Ziel-Element aus wandern sie zu dessen parentNode im HTML-Dokument, dann zum parentNode des parentNode und so weiter, bis sie schließlich ganz oben bei document ankommen (nicht bei window). Sie durchlaufen also die im DOM repräsentierte Dokumentstruktur (vgl. Kapitel 9). Bei Netscape 4 hingegen muss man nicht in den Quelltext sehen, um den Event-Weg zu ermitteln, sondern auf die Referenzierung des Zielelements. Ist das Zielelement
window.document.einDiv.document.links[0]
dann ist genau das auch die Liste und die Reihenfolge der Objekte, die der Event durchwandert: window, window.document, window.document.einDiv usw. (siehe Abbildung 11.1).
Abbildung 11.2: Event-Weg im Beispieldokument bei Internet Explorer
Event-Weg beim W3C
Beim W3C konnte man sich offenbar nicht zwischen Sicker- und Blubber-Modell entscheiden und hat kurzerhand beide übernommen. Der Event-Weg wird allerdings in beiden Richtungen wie beim Internet Explorer von der Dokumentstruktur bestimmt. In W3C-kompatiblen Browsern trifft ein Maus-Event zuerst beim document-Objekt ein, sickert von dort durch die Elementhierarchie bis zum Zielelement und blubbert anschließend rückwärts wieder nach oben bis zum document.
Sehen wir uns jetzt noch einmal die im vorigen Abschnitt erwähnte dritte Argumentstelle von addEventListener() an:
zielElement.addEventListener("eventTyp", anweisung, capture)
Dieser Parameter gibt an, ob der Event auf seinem Weg nach unten abgefangen werden soll (in dem Fall ist er true) oder erst auf seinem Rückweg nach oben (false).
Mit Version 4 von Internet Explorer und Netscape wurde das Objekt event eingeführt (kleines e, nicht zu verwechseln mit dem Objekt window.Event (großes E), das bei Netscape die Event-Konstanten enthält). Das event-Objekt repräsentiert ein gerade eingetroffenes Ereignis, also zum Beispiel einen Klick oder eine Mausbewegung, und stellt nützliche Informationen darüber zur Verfügung.
Die Umsetzung ist allerdings bei Netscape und Microsoft denkbar unterschiedlich:
Beim Internet Explorer ist event eine Eigenschaft von window und kann folglich als window.event angesprochen werden.
window.event existiert nur, wenn gerade ein Ereignis verarbeitet wird, also zwischen dem Eintreffen des Ereignisses am Ziel-Element und seinem Verschwinden am oberen Ende der Element-Hierarchie. Da nie zwei Events genau gleichzeitig verarbeitet werden, reicht ein einziges event-Objekt aus.
Bei Netscape hingegen, und ebenso beim W3C, gibt es kein Objekt namens window.event. Das event-Objekt wird vielmehr automatisch als Parameter an die Handlerfunktion übergeben. Wenn Sie also einen MouseOver-Handler registriert haben:
Element.onmouseover = mouseOverHandler;
dann wird beim Überfahren des Elements mit der Maus das event-Objekt an die Funktion mouseOverHandler() übergeben, wo Sie es weiter bearbeiten können:
function mouseOverHandler(e) { // ... event-Objekt hier als e verfügbar }
Auch bei Event-Handlern, die inline in HTML-Tags definiert sind, steht bei Netscape 4 (nicht aber beim W3C) übrigens das event-Objekt zur Verfügung, und zwar unter dem vordefinierten Namen event:
<a href="#" onClick="alert(event.type)">klick mir</a>
Die Eigenschaft type steht für den Typ des eingetroffenen Ereignisses, in diesem Fall also click. Die wichtigsten Eigenschaften des event-Objekts sind in Tabelle 11.1 zusammengefasst.
Netscape | IE 4/5 | W3C | Beschreibung |
---|---|---|---|
modifiers | altKey ctrlKey shiftKey |
altKey ctrlKey shiftKey |
Gibt an, ob die alt-, crl- oder shift-Taste gedrückt war, als der Event eintrat. Die möglichen modifiers-Werte bei Netscape entsprechen den Konstanten Event.ALT_MASK, Event.CONTROL_MASK und Event.SHIFT_MASK. Die drei Eigenschaften bei IE und W3C sind vom Typ Boolean. |
pageX pageY |
clientX clientY |
clientX clientY |
Enthält bei Maus-Events die x- bzw. y-Koordinate des Mauszeigers - bei Netscape relativ zum document, bei IE und W3C dagegen relativ zum window. Um für IE den Wert relativ zum document zu bekommen, müssen Sie zu event.clientX und clientY document.body.scrollLeft bzw. scrollTop addieren. |
screenX screenY |
screenX screenY |
screenX screenY |
Liefert ebenfalls die x- bzw. y-Koordinate des Mauszeigers, diesmal aber relativ zum Bildschirm |
target | srcElement | target | Eine Referenz auf das Ziel-Element des Events. Bei einem Klick z.B. das angeklickte Element. |
type | type | type | ein String, der den Typ des Events angibt, z.B. click, load oder keyup |
which | keyCode | (in DOM 2 noch nicht enthalten) | Enthält bei Tatstatur-Events den Unicode (IE) bzw. ASCII-Code (Netscape) der gedrückten Taste |
which | button | button | Gibt bei Maus-Events an, welche der Maustasten gedrückt ist: bei Netscape 1 für links, 2 für Mitte, 3 für rechts, bei IE 1 für links, 2 für rechts und 4 für Mitte |
Tabelle 11.1: Eigenschaften des event-Objekts
Standard-Reaktion
Manche HTML-Elemente besitzen eine Standard-Reaktion auf gewisse Ereignisse. Beispielsweise führt ein Klick auf ein a-Element gewöhnlich dazu, dass dessen href-Wert angesprungen wird. Sie können mit JavaScript die Ausführung solcher Standard-Reaktionen verhindern, indem Sie den Event abfangen und einen entsprechenden return-Wert zurückgeben. Dieser Wert ist aus nicht nachvollziehbaren Gründen je nach Event-Typ mal false und mal true: return true müssen Sie zum Beispiel verwenden, um die Statusanzeige eines Links bei mouseover zu verhindern. Mit return false blockieren Sie Klick- und Submit-Events.
Die Standard-Reaktion des HTML-Elements erfolgt immer erst, nachdem der Event seinen Weg durch die Elementhierarchie beendet hat. Ein Event kommt also zwar im Internet Explorer zuerst beim Ziel-Element an und blubbert dann bis zum document hoch, aber erst danach löst er die Standard-Reaktion des Ziels aus.
Sie können deshalb so für IE alle Links auf einer Seite deaktivieren:
function klickHandler() { return false; }
document.onclick = klickHandler;
Event-Weiterleitung
Das Weiterblubbern des Events wird durch die return-Angabe nicht behindert. Für diesen Zweck besitzt window.event die Eigenschaft cancelBubble. Setzen Sie diese auf true, wird das Blubbern für das aktuelle Ereignis abgestellt.
Bei Netscape ist es natürlich ganz anders: Ein mit captureEvents() abgefangener Event wird von alleine nicht mehr weiter nach unten geleitet. Haben Sie also auf document-Ebene Klicks abgefangen, dann werden onclick-Handler in Links oder Buttons nie angesprochen. (Die Standard-Reaktion der Links und Buttons wird aber trotzdem ausgeführt, solange Sie nicht den entsprechenden return-Wert angeben!)
Um einen Event bei Netscape wieder zurück auf seinen Weg zu schicken, gibt es routeEvent():
function klickHandler(e) { ... irgendwelche Anweisungen ... // Jetzt wird der Event von document aus weitergeschickt: document.routeEvent(e); } document.captureEvents(Event.CLICK); document.onclick = klickHandler;
Das Event-Modell des W3C sieht für die beschriebenen Aufgaben zwei neue Methoden des event-Objekts vor:
stopPropagation() verhindert die Weiterleitung des Events,
preventDefault() blockiert die Ausführung der Standard-Reaktion.
Mit Hilfe der Event-Handler onkeydown, onkeyup und onkeypress erfahren Sie, welche Tasten Ihr Besucher gerade auf seiner Tastatur drückt oder loslässt. Damit können Sie zum Beispiel ein tastaturgesteuertes Menü oder Spiel bauen oder sogar eigene Formular-Elemente erzeugen.
Leider unterstützt die Unix-Version von Netscape 4 keine Tastatur-Events. Sie sollten also immer Alternativen für Unix-/Linux-Benutzer bereithalten.
Auch Tastatur-Events besitzen ein Ziel-Element, zum Beispiel ein textarea-Feld in einem Formular. Um Tastendrücke außerhalb von Formularen abzufangen, registrieren Sie Ihren Handler am besten auf document-Ebene:
document.onkeydown = tasteUntenHandler;
Netscape 4 gerät allerdings leicht in Verwirrung, wenn neben Tastatur-Events auch gleichzeitig Maus-Events eintreffen oder Texte selektiert sind. Diese Verwirrungen können Sie verhindern, indem Sie captureEvents() benutzen:
document.captureEvents(Event.KEYDOWN); document.onkeydown = tasteGedrueckt;
Mit captureEvents() bekommen Sie nun natürlich auch alle Eingaben, deren Ziel gar nicht document war, sondern etwa ein Formular. Beim Internet Explorer erhalten Sie diese ohnehin, weil sie von ihrem Ziel-Element aus zu document hochblubbern.
Welche Taste gedrückt wurde, erfahren Sie über spezielle Eigenschaften des event-Objekts: event.which bei Netscape und event.keyCode beim Internet Explorer. Damit erhalten Sie aber nicht das Zeichen selbst, sondern dessen Keycode, ein Integer-Wert. Die Funktion String.fromCharCode(derKeyCode) liefert dann das dazugehörige Zeichen.
Leider bekommt man mit Internet Explorer und Netscape nicht immer dieselben KeyCodes, weil Internet Explorer bei Keyup und Keydown standardmäßig die Codes der Großbuchstaben liefert, Netscape 4 aber Modifier-Tasten wie Shift, Alt und Ctrl mit einberechnet, also bei nicht gedrückter Umschalttaste Kleinbuchstaben liefert. Netscape 6 verhält sich in der Hinsicht wie der Internet Explorer. Immerhin: Bei Keypress verhalten sich alle Browser gleich und liefern den Code des tatsächlich eingegebenen Zeichens, unter Berücksichtigung aller Modifier-Tasten. Um diesen Wert bei Keyup und Keydown zu bekommen, müssen Sie für Internet Explorer und Netscape 6 event.shiftKey abfragen.
Sollten Sie neben gewöhnlichen Zeichen, Shift, Alt und Ctrl auch zum Beispiel Funktions- oder Cursortasten abfangen wollen, dann macht Ihnen Netscape 4 einen Strich durch die Rechnung: Auf manche dieser Tasten reagiert er überhaupt nicht, für andere liefert er immer nur den KeyCode 0. Mit Internet Explorer und Netscape 6 hingegen können Sie fast alle Tasten abfragen ( allerdings mit Internet Explorer nur über die Handler onkeydown und onkeyup, nicht über onkeypress, denn dieser reagiert nur auf gewöhnliche Zeichen.
Das W3C, rettendes Ufer der von solchen Inkompatibilitäten geplagten Webdesigner, hat sich aus dem Thema Tastatur-Events bislang herausgehalten. Erst Level 3 der DOM-Spezifikation, die zur Drucklegung dieses Buches noch nicht verfügbar ist, wird sich dazu äußern.
In der Zwischenzeit bleibt uns nichts anderes übrig, als wie gewohnt die unterschiedlichen Modelle unter einen, manchmal unschönen und komplizierten, Hut zu stecken.
Wenn Sie die entgegengesetzten Event-Modelle von Netscape, Microsoft und W3C einsetzen wollen, lassen Sie Ihre Kopfschmerztabletten erst noch im Regal und lesen Sie diesen Abschnitt zu Ende. Es ist nämlich gar nicht so schwierig, wenn man ein paar Grundregeln einhält.
Die erste ist: Alles ausprobieren! Verlassen Sie sich auf nichts. Microsoft und vor allem Netscape überraschen oft mit höchst unvorhersagbarem Verhalten.
Zweitens: Verwenden Sie Event-Handler möglichst nur dort, wo alle Browser Sie verstehen. Internet Explorer interpretiert zum Beispiel onmouseover bei fast jedem Element, Netscape 4 dagegen nur bei <a>, <area> und <layer>. Schreiben Sie also statt:
<p onMouseOver="tuwas()">ein Text</p>
besser:
<p> <a href="javascript:void(0)" onMouseOver="tuwas()" style="text-decoration:none; cursor:text">ein Text</a> </p>
Die CSS-Angaben verhindern, dass der Link wie ein gewöhnlicher Link aussieht. Als href-Wert ist javascript:void(0) angegeben, was bedeutet, dass nichts passiert, wenn man den Link anklickt.
Wie fängt man Events auf einer höheren Ebene im Dokument ab? So:
if (document.captureEvents) // Browser ist Netscape document.captureEvents(Event.MOUSEUP); document.onmouseup = tuwas;
Dass der Event bei Netscape abgefangen wird, bevor er sein Ziel erreicht hat, bei IE aber erst danach, spielt in der Regel keine Rolle. Nur wenn Sie auch auf anderen Ebenen denselben Event abfangen wollen, müssen Sie sich Gedanken um die Reihenfolge machen und für Netscape routeEvent() angeben.
Sie können Sie die Reihenfolge der aufgerufenen Handlerfunktionen bei Netscape der beim Internet Explorer anpassen, indem Sie routeEvent() gleich am Anfang Ihres Handlers aufrufen:
<script> if (document.captureEvents) document.captureEvents(Event.MOUSEUP); document.onmouseup = tuwas; function tuwas(e) { if (e) document.routeEvent(e); alert("MouseUp auf document"); } </script> <a href="#" onMouseUp="alert('MouseUp auf Link')"> Klick klack </a>
Probieren Sie dieses Beispiel einfach auf der beiligenden CD aus.
Die W3C-spezifischen Methoden wie addEventListener sind für die Praxis noch kaum von Bedeutung, da sie derzeit (Sommer 2000) nur von Netscape 6 unterstützt werden. Darüber hinaus sind die alten Vorgehensweisen zur Event-Handler-Registrierung über Objekt-Eigenschaften oder in HTML-Tags laut W3C auch weiterhin erlaubt.
Anders als das Objektmodell von Netscape 6 ist sein Event-Modell übrigens weitgehend abwärtskompatibel: Er versteht (voraussichtlich) neben einem Großteil der W3C-Spezifikation auch die meisten Eigenheiten von Netscape 4, einschließlich captureEvents() und routeEvent().
Schließlich bleibt noch die Frage, wie man in einem Event-Handler am besten auf das event-Objekt zugreift. Oft ist folgende Technik ganz praktisch:
function handlerFunktion(e) { var e = e || window.event; ... irgendwelche Anweisungen ... }
Die Variable e steht damit sowohl bei IE als auch bei Netscape für das jeweilige event-Objekt. Bei der Verwendung von e müssen die je nach Browser unterschiedlichen Bezeichnungen für die Event-Eigenschaften natürlich berücksichtigt werden.
Einige der folgenden Übungen (und zwar 6, 7, 9, 10, 12, 13 und 17) setzen grundlegende Kenntnisse in DHTML voraus. Konkret sollten Sie wissen, wie man mit den verschiedenen Browsern Elemente absolut positioniert, versteckt, wieder anzeigt und herumbewegt. Wenn Sie Kapitel 9 und 10 gelesen haben, sind Sie bestens vorbereitet.
leicht
Es gibt zwei verbreitete Techniken zum Registrieren von Event-Handlern. Welche sind das? Überlegen Sie sich Vor- und Nachteile der jeweiligen Methode.
leicht
Was ist hier falsch?
<script> window.onUnload = "alert('Auf Wiedersehen')"; </script>
leicht
Eine Seite, die so beginnt, liefert bereits beim Laden eine Fehlermeldung:
<html> <head> <title></title> <script> function formularCheck(){ alert("Danke, dass Sie das Formular abschicken!"); } document.einFormular.onsubmit = formularCheck; </script> ...
Warum?
mittel
Abbildung 11.2 ist ein wenig irreführend: Sie stellt den TextNode "ein Link" als Event-Ziel dar. Das ist korrekt nach dem Event-Modell des W3C, aber falsch für das des Internet Explorers: Bei diesem können nur HTML-Elemente Events empfangen, nicht aber TextNodes. Klickt man auf einen Link, ist event.target also bei W3C-kompatiblen Browsern nicht das Link-Element, sondern dessen childNode, der Text des Links. Beim Internet Explorer ist event.srcElement dagegen der Link. Warum spielt dieser Unterschied für die Praxis keine große Rolle?
mittel
Ein Klick auf diesen Link führt sowohl beim Internet Explorer als auch bei Netscape zur Ausgabe von "click":
<a href="#" onClick="alert(event.type)"> Die Tatsachen im logischen Raum sind die Welt. </a>
Weshalb?
Schreiben Sie den Code so um, dass der Event-Handler als Objekt-Eigenschaft registriert wird. Da der Link der erste und einzige auf der Seite ist, können Sie ihn mit document.links[0] ansprechen.
mittel
Vielleicht haben Sie auch schon eine dieser Seiten gesehen, wo der Mauszeiger von einem Haufen kleiner Sternchen umschwärmt wird oder eine Kette von Bällen hinter sich her zieht. Fangen wir klein an: Erstellen Sie mit einem Grafikprogramm ein Bildchen und schreiben Sie anschließend eine Seite, auf der der Mauszeiger von diesem Bild verfolgt wird. Versuchen Sie eine Lösung für Netscape und Internet Explorer ab Version 4.
Um die Mausposition zu erhalten, verwenden Sie für Netscape 4 und 6 die Eigenschaften pageX und pageY des event-Objekts, für IE addieren Sie, wie in Tabelle 11.1 vorgeschlagen, zu clientX den Wert von document.body.scrollLeft und zu clientY den von document.body.scrollTop.
leicht
Vielleicht erinnern Sie sich: 1999 wurde auf Seiten, die den kostenlosen Speicherplatz eines bestimmten Anbieters nutzten, dessen Logo automatisch in der rechten unteren Fensterecke eingeblendet. Beim Scrollen wanderte das Logo mit und blieb so immer in der Ecke stehen. Diese Idee lässt sich auch sinnvoll einsetzen, zum Beispiel um ein Menü jederzeit im Fenster zu halten.
Entwerfen Sie eine Seite mit einem solchen Menü. Es soll immer 100 Pixel unter dem oberen Fensterrand auf der linken Seite des Dokuments stehen.
leicht
Wenn man unter Windows mit der rechten Maustaste auf eine Webseite klickt, erscheint das Kontextmenü des Browsers, das einem praktische Funktionen zur Verfügung stellt: Seite als Bookmark speichern, neu laden, in neuem Fenster öffnen, Quelltext anzeigen, Bild speichern, auf vorige Seite zurückspringen und so weiter.
Angenommen, Sie wollen Ihre Besucher ärgern, indem Sie ihnen dieses Menü wegnehmen. Wie würden Sie dazu vorgehen? Schreiben Sie eine Testseite, die sowohl mit Internet Explorer 5 als auch mit Netscape 4 funktioniert.
Verwenden Sie für IE5 den Handler document.oncontextmenu, für Netscape 4 document.onmousedown.
schwer
Entwerfen Sie eine HTML-Seite mit einem absolut positionierten div-Element, das vier Links enthält. Geben Sie dem Element mit Hilfe dieser CSS-Angaben eine graue Hintergrundfarbe:
background-color:lightGrey; layer-background-color:lightGrey;
Das Menü soll erst sichtbar werden, wenn ein bestimmtes Bild auf der Seite angeklickt wird. Verlässt man mit dem Mauszeiger das Menü, soll es wieder verschwinden.
Schreiben Sie zuerst den Code für Netscape 4, passen Sie ihn dann für Internet Explorer (ab Version 4) an, und bringen Sie schließlich das Ganze auch auf W3C-Browsern wie Netscape 6 zum Laufen.
leicht
Kombinieren Sie jetzt die beiden letzten Übungen, um eine Art eigenes Kontextmenü zu bauen: Wenn man die rechte Maustaste herunterdrückt, soll neben dem Mauszeiger ein Menü mit Links erscheinen. Anders als das gewöhnliche Kontextmenü verschwindet es, sobald man es mit der Maus verlässt.
leicht
Die nächsten Übungen beschäftigen sich mit Tastatur-Events.
Nehmen wir an, in ein Formularfeld vom Typ input type="text" soll eine Zahl eingegeben werden. Anstatt erst beim Absenden den Inhalt zu prüfen, fangen Sie gleich bei der Eingabe die Zeichen ab: Wenn keine Ziffer gedrückt wurde, blockieren Sie die Eingabe, indem Sie im onkeydown-Handler den Wert false zurückgeben. Es ist damit von Anfang an unmöglich, in das Feld etwas anderes als eine Zahl zu schreiben.
mittel
Erstellen Sie jetzt ein eigenes Text-Eingabefeld. Als Grundlage verwenden Sie folgenden HTML-Code:
<div id="rahmen" style="position:absolute; left:5px; top:40px; width:400px; height:100px; background-color:#eeeeee; layer-background-color:#eeeeee; clip:rect(0px 400px 100px 0px)"> <div id="feld" style="position:absolute; left:5px; top:2px;">|</div> </div>
Schreiben Sie ein JavaScript, das bei jedem Tastendruck das entsprechende Zeichen in das div mit der ID feld schreibt. Das Skript braucht nur auf Internet Explorer ab Version 4 und Netscape 4 zu funktionieren. Wenn Sie es sich zutrauen, versuchen Sie auch die Möglichkeit einzubauen, mit der Backspace-Taste bereits eingegebenen Text wieder zu löschen.
Abbildung 11.3: eigene Formularelemente dank Event-Handling und DHTML
schwer
Jetzt geht es ein bisschen zur Sache. Schreiben Sie ein Skript, das, wenn man es in den head einer Seite einbaut, alle positionierten div-Elemente auf der Seite Drag&Drop-fähig macht.
Gemeint ist Folgendes. Angenommen, auf einer Seite befinden sich drei div-Blöcke, die mit CSS-Angaben absolut positioniert sind. Nun sind Leute, die im Internet surfen, bekanntlich von zappeliger psychischer Konstitution, und bereits nach wenigen auf einer Seite verbrachten Stunden wird ihnen das Layout langweilig. Bieten Sie deshalb die Möglichkeit, dieses Layout per Drag&Drop nach eigenem Geschmack zu verändern: Der Besucher drückt mit der Maus auf eins der div-Elemente, bewegt es mit gedrückter Maustaste herum und lässt es irgendwo stehen, indem er die Taste loslässt. Perfekt wird das Ganze, wenn Sie die aktuellen Positionen in einem Cookie speichern und beim nächsten Besuch auf der Seite das Layout wieder so herstellen, wie es der Besucher verlassen hat.
Stellen Sie das Cookie-Feature erst einmal hinten an und beginnen Sie, das Drag&Drop-Skript umzusetzen. Am besten machen Sie das zuerst für nur ein div-Element und erweitern das Skript anschließend für beliebig viele Elemente. Aber Vorsicht: Sie wissen nicht, was im body-Bereich der Seite steht! Insbesondere kennen Sie die Anzahl und IDs der div-Elemente nicht.
Und noch etwas: Wenn zwei div-Elemente sich überlappen, dann soll immer dasjenige oben liegen, das zuletzt durch ein Drag&Drop aktiviert wurde.
leicht
Eine Besonderheit unter den Events ist das error-Ereignis. Den onerror-Handler gibt es für Image-Objekte und für window. Bei einem Bild feuert onerror, wenn es nicht korrekt geladen wurde. window.onerror dagegen wird immer dann aufgerufen, wenn ein JavaScript-Fehler eintritt.
Zuerst zu Image.onerror. Vielleicht haben Sie auf Ihrer Seite ein Bild, das nicht immer verfügbar ist ( zum Beispiel einen dieser kostenlosen Zugriffszähler, die andauernd ausfallen. Wenn der Zähler nicht geladen werden kann, wollen Sie nicht das schmucke "Bild defekt"-Icon des Browsers anzeigen, sondern ein eigenes Alternativbild. Mit einem Error-Handler ist das ganz einfach:
<img src="http://prima.CGI-Service.de/meinCounter.pl" name="zaehler" width="200" height="50" onerror="this.src='meinAlternativBild.jpg'">
Dieser Handler erlaubt es auch, mit JavaScript zu testen, ob ein bestimmter Server derzeit erreichbar ist. Überlegen Sie sich, wie das geht, und schreiben Sie eine Testseite.
mittel
Sehen wir uns nun window.onerror an.
Error-Handling
Fehlerbehandlung ist ein unverzichtbarer Bestandteil vieler Anwendungen. Die Ihnen vielleicht aus Java bekannten Operatoren try und catch stehen in JavaScript jedoch erst ab Internet Explorer 5 und Netscape 6 zur Verfügung. Aus Kompatibilitätsgründen sollten Sie deshalb lieber auf den Error-Event setzen.
Weist man window.onerror eine Funktion zu, dann wird diese jedesmal aufgerufen, wenn ein JavaScript-Fehler auftritt. Die Standard-Reaktion, bei vielen Browsern ein unschönes Popup-Fenster, das auf den Fehler hinweist, wird durch den Rückgabewert true unterdrückt.
Sie können deshalb mit folgenden Zeilen die Anzeige aller JavaScript-Fehler auf einer Seite verhindern:
function errorHandler() { return true; } window.onerror = errorHandler;
Verwechseln Sie nicht JavaScript-Fehler mit serverseitigen Fehlermeldungen, die zu den "Browser-Errors" gezählt werden. Bei der 404-Meldung, die eine nicht gefundene Seite anzeigt, handelt es sich zum Beispiel nicht um einen JavaScript-Fehler, der onerror-Handler wird deshalb nicht angeworfen. window.onerror reagiert nur auf Javascript-Fehler.
Beachten Sie: Beim Eintreten eines Fehlers wird der gegenwärtige Skriptblock nicht weiter abgearbeitet.
Ein Skriptblock ist eine zusammenhängende Einheit von JavaScript-Code, die an einem Stück ausgeführt wird. Das hier ist zum Beispiel ein Skriptblock:
alert("vorher"); irgendeineFunktion(); unbekannteAnweisung(); alert("nachher");
Wenn unbekannteAnweisung() einen Fehler erzeugt, wird die zweite alert-Box nie angezeigt, egal welche Art von Fehlerbehandlung Sie bei window.onerror durchführen.
Nehmen wir jetzt an, Sie wollen eine riskante Anweisung ausführen, bei der ein Fehler auftreten kann. Diese Anweisung lautet riskanteAnweisung(). Bei einem Fehler soll in der Statuszeile der Text "Fehler!" angezeigt werden. Egal was passiert soll aber nach riskanteAnweisung() stets die Funktion weiter() ausgeführt werden. Wie gehen Sie vor?
leicht
Die auffälligste Besonderheit am Error-Event ist, dass er kein event-Objekt erzeugt. Stattdessen werden dem Handler drei Argumente übergeben: Die Fehlermeldung (ein String), der URL der Seite (auch ein String) und die Zeilennummer, an der der Fehler auftrat (eine Zahl).
Schreiben Sie eine Seite, auf der bei jedem Javascript-Fehler ein Fenster aufgeht, in dem die wichtigsten Informationen über den Fehler stehen: Fehlermeldung, Zeilennummer, URL und die benutzte Browserversion. Über einen Formularbutton soll der Besucher diese Informationen an den Webmaster mittels eines CGI-Mailers abschicken können.
mittel
Zum Abschluss noch eine lustige Übung mit dem W3C DOM, also für Internet Explorer 5 und Netscape 6:
Schreiben Sie ein Skript, das bei jedem Mausklick das angeklickte Seitenelement aus dem Dokument entfernt.
Machen Sie dann daraus ein "Bookmarklet". Dazu stellen Sie das ganze Skript in eine einzige Zeile und schreiben "javascript:" davor. Also:
javascript:hier_stehen_die_ganzen_Anweisungen
Achten Sie darauf, dass die Anweisungen keinen Rückgabewert haben. Um Rückgabewerte zu unterdrücken, verpacken Sie Funktionsaufrufe und Zuweisungen in den Operator void, zum Beispiel:
void(eigenschaft = wert)
Testen Sie das Skript, indem Sie es in die Zwischenablage kopieren und in der Adresszeile des Browsers einfügen. Nicht abgefangene Rückgabewerte erkennen Sie leicht daran, dass Sie im Browserfenster angezeigt werden. Wenn Ihr Skript funktioniert, erstellen Sie ein neues Lesezeichen (Bookmark, Favorit) und fügen das Skript ins URL-Feld des Lesezeichens ein.
Wenn Sie von nun an auf eine Seite geraten, wo Ihnen z.B. ein Werbebanner oder ein Textabsatz nicht gefällt, dann rufen Sie einfach das Lesezeichen auf und klicken den Banner oder Absatz weg.
Event-Handler können als HTML-Attribute definiert werden:
<a href="link.html" onClick="alert('Danke für diesen Klick!')">
oder als Eigenschaften der entsprechenden JavaScript-Objekte:
document.links[0].onclick = sagDanke;
Die erste Methode ist bei einfachen Anweisungen oft handlicher und verlangt weniger Schreibarbeit. Besonders angenehm ist, dass man einer aufgerufenen Funktion nach Belieben Argumente mit auf den Weg geben kann:
<a href="link.html" onClick="tuwas(1, 'zwei', drei)">
Bei der zweiten Technik ist das zumindest nicht ohne Weiteres möglich:
document.links[0].onclick = tuwas;
Diese Vorgehensweise ist dafür wesentlich flexibler: Sie erlaubt es, jederzeit einen Event-Handler neu zu registrieren (nicht nur beim ersten Laden der Seite) und zu ändern, außerdem kann die Registrierung an beliebigen Stellen im JavaScript-Code erfolgen, zum Beispiel in einer if - else Anweisung:
if (document.layers) window.onresize = resizeBugFix;
Darüber hinaus wird so der dargestellte Inhalt der Seite klar vom Script getrennt, was die Übersicht erhöht und allgemein als guter Stil gilt.
Statt einer Referenz auf eine JavaScript-Anweisung wird window.onunload ein String zugewiesen. Außerdem muss onunload in Kleinbuchstaben geschrieben werden. Richtig ist es so:
<script> function unloadHandler() { alert("Auf Wiedersehen"); } window.onunload = unloadHandler; </script>
Zum Zeitpunkt der Ausführung von
document.einFormular.onsubmit = formularCheck;
existiert noch kein Formular. Dieses wird vermutlich erst später, im body, erzeugt.
Sie sollten Event-Handler immer erst registrieren, wenn Sie sich darauf verlassen können, dass das entsprechende Objekt auch existiert. Sicher sind Sie, wenn Sie die Registrierungen in einer init()-Funktion durchführen, die onload aufgerufen wird.
Es spielt keine große Rolle, weil sowohl bei IE als auch beim W3C Events durch die DOM-Hierarchie blubbern. Das bedeutet, dass der Klick-Event vom TextNode als Nächstes beim Link-Element eintrifft. In der Regel wird man auf dieser Ebene den Klick abfangen wollen - schon allein deshalb, weil der TextNode schwerer anzusprechen ist: Man kann ihm ja im HTML-Code keine ID verpassen, und einen Start-Tag für Handler-Attribute hat er erst recht nicht.
Bemerkbar macht sich der Unterschied allerdings, wenn man das event-Objekt nach seinem Ziel befragt. Dann liefern Internet Explorer und W3C-kompatible Browser unterschiedliche Antworten: IE den Link, W3C-Browser dessen Text-Wert.
Beim Internet Explorer ist das event-Objekt eine Eigenschaft von window. Wie bei allen window-Eigenschaften kann man window.event auch direkt als event ansprechen. Daher verweist der Ausdruck event in als HTML-Attribut definierten Event-Handlern sowohl bei IE als auch bei Netsape auf das event-Objekt - bei Netscape natürlich nicht auf window.event, sondern auf den versteckten Parameter namens event, der automatisch an den Handler übergeben wird.
Bei beiden Browsern zeigt event.type den Event-Typ an, in diesem Fall "click".
So sieht der Code aus, wenn man den Event-Handler als Objekt-Eigenschaft registriert:
<a href="#"> Die Tatsachen im logischen Raum sind die Welt. </a> <script> function klickHandler(e) { e = e || window.event; alert(e.type); } document.links[0].onclick = klickHandler; </script>
Beachten Sie, dass diese Version des Klick-Handlers nicht funktioniert:
function klickHandler(event) { alert(event.type); }
Da bei Internet Explorer kein Parameter an den Handler übergeben wird, ist event hier undefiniert, es tritt daher eine Fehlermeldung auf. (Die Argumente einer Funktion werden in der Funktion als lokale Variablen interpretiert und verdecken damit gleich lautende Eigenschaften von window.)
Es gibt wie immer verschiedene Ansätze, das umzusetzen. Hier eine der möglichen Lösungen:
<html> <head> <title>Maus-Verfolgung</title> <style type="text/css"> #verfolger { position:absolute; left:-50px; top:-50px; width:50px; height:50px; } </style> <script language="JavaScript1.2" type="text/javascript"> function init() { if(document.captureEvents) // Netscape document.captureEvents(Event.MOUSEMOVE); document.onmousemove = verfolgen; } window.onload = init; function verfolgen(e) { if (document.layers) { // Netscape 4 document.verfolger.left = e.pageX+5; document.verfolger.top = e.pageY; } else if (document.all) { // IE verf = document.all.verfolger.style; verf.left = event.clientX+document.body.scrollLeft+5; verf.top = event.clientY+document.body.scrollTop; } else if (document.getElementById) { // Netscape 6 verf = document.getElementById("verfolger"); verf.style.left = e.pageX+5; verf.style.top = e.pageY; } } </script> </head> <body> <div id="verfolger"> <img src="verfolger.gif" width="50" height="50" alt=""> </div> ... </body> </html>
Das Bild wird immer um 5 Pixel nach rechts versetzt, damit es nicht mit einer Ecke direkt unter dem Mauszeiger liegt und versehentlich alle Klicks abfängt.
Eine richtige "Verfolgung" ist das zugegeben noch nicht: Das Bild läuft immer brav rechts neben dem Zeiger her.
Das nächste Listing zeigt einen anderen Lösungsansatz, der dieses Manko behebt: Bei Mousemove-Ereignissen werden die aktuellen Mauskoordinaten in zwei globalen Variablen festgehalten. Per setInterval() bewegt sich alle 50 ms der Verfolger auf die Maus zu, und zwar umso schneller, je weiter er von ihr weg ist.
<html> <head> <title>Maus-Verfolgung II</title> <script language="JavaScript1.2" type="text/javascript"> // Die Mauskoordinaten: var mausX = -50; var mausY = -50; function init() { // gemeinsame Referenz auf Verfolger-div erzeugen: verf = (document.layers) ? document.verfolger : (document.all) ? document.all.verfolger.style : document.getElementById("verfolger").style; if(document.captureEvents) // Netscape document.captureEvents(Event.MOUSEMOVE); document.onmousemove = koordinatenUpdate; setInterval("verfolgen()", 50) } window.onload = init; function koordinatenUpdate(e) { // mausX und mausY aktualisieren mausX = (e) ? e.pageX + 5 : event.clientX + document.body.scrollLeft + 5; mausY = (e) ? e.pageY : event.clientY + document.body.scrollTop;
} function verfolgen() { // verfolger verschieben: verf.left = parseInt(verf.left) + (mausX - parseInt(verf.left))/4; verf.top = parseInt(verf.top) + (mausY - parseInt(verf.top))/4; } </script> </head> <body> <div id="verfolger" style="position:absolute; left:-50px; top:-50px; width:50px; height:50px;"> <img src="verfolger.gif" width="50" height="50" alt=""> </div> ... </body> </html>
Anders als im vorigen Listing wird hier eine Variable verf angelegt, die bei allen Browsern auf das Verfolger-Element (beziehungsweise dessen style-Eigenschaft) verweist. Damit spare ich mir einige Browser-Verzweigungen. In der Funktion verfolgen() benutze ich die JavaScript-Funktion parseInt(), um die aktuellen Verfolger-Koordinaten auszulesen, weil Internet Explorer und Netscape 6 hier einen String liefern, z.B. "260px". parseInt() extrahiert den Zahlenwert am Anfang eines Strings, macht also aus "260px" 260.
Bestimmt ist Ihnen der Einsatz des Konditional-Operators aufgefallen:
(bedingung)? ausdruck1 : ausdruck2;
Das ist, wenn es sich bei den Ausdrücken um Anweisungen handelt, gleichbedeutend mit
if (bedingung) ausdruck1; else ausdruck2;
Im Gegensatz zu if - else kann aber der Fragezeichen-Operator auch auf Ausdrücke angewendet werden, die keine Anweisungen sind. Sehen Sie sich die Verwendung im obigen Beispiel einfach einmal genauer an.
Die Funktion menuPos() misst, um wie weit die Seite gescrollt ist und verschiebt das Element mit der ID menu an die entsprechende Stelle:
function menuPos(){ var gescrollt = (document.all)? document.body.scrollTop : window.pageYOffset; var neuPos = gescrollt + 100; if (document.layers) // Netscape 4 document.menu.top = neuPos; else if (document.all) // IE document.all.menu.style.top = neuPos; else if (document.getElementById) // W3C document.getElementById("menu").style.top = neuPos; }
menuPos() wird, sobald die Seite geladen ist, alle 40 Millisekunden aufgerufen:
function init() { setInterval("menuPos()",40); } window.onload = init;
Damit ist der Skript-Teil beendet.
Die Definition des Menüs in HTML könnte etwa so aussehen:
<div id="menu"> <a href="startseite.html">Startseite</a><br> <a href="klaviere.html">Klaviere</a><br> <a href="karinetten.html">Klarinetten</a><br> <a href="sitemap.html">Sitemap</a><br> </div>
Die Positionierung wird in einem style-Element im head festgelegt. Dort kann man auch gleich den restlichen Seiteninhalt soweit nach rechts verschieben, dass das Menü daneben passt:
<style type="text/css"> #menu { position:absolute; left:20px; top:100px; width:180px } body { padding-left:200px } </style>
Nicht so schön an dieser Lösung ist, dass das Menü beim Scrollen, wie einst das Geocities-Logo, ziemlich ruckelt und zappelt. Mit setInterval()immer wieder die Scrollposition abzufragen, ist offenbar nicht sehr effizient.
Beim Internet Explorer kann man diese Schwäche mit dem onscroll-Handler des body-Elements beheben: Dieser reagiert unmittelbar, sobald die Seite gescrollt wird.
Eigentlich ist in der CSS-Spezifikation sogar eine Positionsangabe vorgesehen, die ganz ohne JavaScript den gewünschten Effekt bewirkt: Mit position:fixed positionierte Elemente sollen beim Scrollen fest an den angegebenen Koordinaten (relativ zum Fenster) stehen bleiben. Von den beiden großen Browserherstellern unterstützt diese Angabe bislang nur Netscape 6.
Um das Skript nun für Internet Explorer und Netscape 6 zu verbessern, muss lediglich die init()-Funktion geändert werden:
function init() { // für IE onscroll das Menü neu positionieren: if (document.all) document.body.onscroll = menuPos; // für W3C-Browser position auf "fixed" stellen: else if (document.getElementById) document.getElementById("menu").style.position="fixed"; // für Netscape 4 wie gehabt: else if (document.layers) setInterval("menuPos()",40); }
Der nur beim Internet Explorer 5 verfügbare oncontextmenu-Handler macht es uns leicht: Wir müssen nur document.oncontextmenu eine Funktion zuweisen, die mittels return false die Standard-Reaktion ausschaltet.
Bei Netscape 4 fangen wir alle Mousedown-Events auf document-Ebene ab, prüfen, ob die rechte Maustaste gedrückt war, und verhindern dann ebenfalls die Standard-Reaktion. Hier der Cross-Browser-Code:
if (document.captureEvents) { // Netscape document.captureEvents(Event.MOUSEDOWN); document.onmousedown = mausUnten; } else if (document.all && document.getElementById) { // IE5 document.oncontextmenu = mausUnten; } function mausUnten(e) { // bei Netscape auf rechte Maustaste prüfen if (!e || e.which == 3) { // Standard-Reaktion verhindern return false; } }
Wir könnten auch beim Internet Explorer Mousedown abfangen - immerhin wird das bereits in Version 4 untserstützt. Das würde aber nichts helfen, weil das Kontextmenü hier anders als bei Netscape nicht als Standard-Reaktion auf Mousedown-Ereignisse implementiert ist. Es kann deshalb auch nicht über einen entsprechenden Rückgabewert im onmousedown-Handler blockiert werden.
Erstaunlicherweise gibt es Autoren im Internet, die glauben, mit dem Ausschalten des Kontextmenüs unter Windows lasse sich verhindern, dass jemand den Quelltext der Seite ansieht oder Bilder daraus abspeichert. Das ist natürlich Unsinn. Es gibt tausend andere Wege, an den Quelltext und die Bilder zu kommen - zur Not schaltet man einfach kurz JavaScript ab, und schon hat man sein Kontextmenü wieder. Ein auch nur ansatzweise brauchbarer Schutz vor Quelltext- und Bilderklau ist mit JavaScript nicht möglich.
Die größte Schwierigkeit bei dieser Übung besteht darin, mit dem Hochblubbern der Link-Events zum div bei Internet Explorer und W3C-Browsern fertigzuwerden. Es gibt verschiedene Ansätze dazu.
Sie könnten beispielsweise für alle Menüpunkte einen MouseOut-Handler registrieren und darin event.cancelBubble=true (IE) bzw. event.stopPropagation() (W3C) ausführen.
Oder Sie versuchen, bei jedem Eintreffen eines Mouseout-Events beim div herauszubekommen, ob der Mauszeiger den div-Bereich wirklich verlassen hat. Ich mache das meistens so:
Wenn ein Link überfahren oder verlassen wird, trifft unmittelbar nach dem Mouseout-Event beim div-Objekt auch ein Mouseover ein: entweder vom Link oder vom div-Element. Ich warte also onmouseout kurze Zeit, bevor ich das Menü verstecke. Feuert in der Zwischenzeit onmouseover, lasse ich es sichtbar.
<html> <head> <title>Ein Popup-Menü</title> <script> function init() { // Diese Funktion wird onload ausgeführt. // Sie definiert eine Variable menu, die auf das // div-Element verweist. Dann werden zwei Event-Handler // registriert. menu = (document.all) ? document.all["container"]: (document.layers) ? document.layers["container"]: document.getElementById("container"); menu.onmouseout = menuOut; menu.onmouseover = menuOver; } function menuOut(){ // Bei Netscape 4 kann einfach das Menü ausgeblendet // werden: if (document.layers) menu.visibility="hide"; // Bei IE und W3C aber könnte auch der onmouseout eines // Links gefeuert haben. In dem Fall wird kurz darauf // auch ein Mouseover einschlagen. Deshalb starten wir // einen setTimeout: else versteckTO = setTimeout('menu.style.visibility="hidden"',100); } function menuOver(){ // Falls der bei Mouseout gestartete setTimeout läuft, // halten wir ihn an: if (window.versteckTO) clearTimeout(versteckTO); } function menuAn(){ if (document.layers) menu.visibility="show"; else menu.style.visibility="visible"; } </script> <style type="text/css"> #container { position:absolute; left:70px; top:20px; width:220px; visibility:hidden; padding:5px; background-color:lightGrey; layer-background-color:lightGrey; } </style> </head> <body onload="init()"> <a href="javascript:void(0)" onClick="menuAn(); return false"> <img src="bild.jpg" width="50" height="50" border="0"> </a> <div id="container"> <a href="link1.html" class="menu">Seinstreue</a><br> <a href="link2.html" class="menu">Selbstverzweigung</a><br> <a href="link3.html" class="menu">Farnwalduniversum</a><br> <a href="link4.html" class="menu">Samtallergie</a><br> </div> </body> </html>
Kommentare im Quelltext zeigen an, wo sich im Vergleich zur Lösung der vorigen Übung etwas geändert hat.
<script> function init() { menu = (document.all) ? document.all["container"] : (document.layers) ? document.layers["container"] : document.getElementById("container"); menucss = (document.layers) ? menu : menu.style; menu.onmouseout = menuOut; menu.onmouseover = menuOver; // den Handler für die rechte Maustaste registrieren: if (document.captureEvents) { document.captureEvents(Event.MOUSEDOWN); document.onmousedown = menuAn; } else if (document.all && document.getElementById) { document.oncontextmenu = menuAn; } } function menuOut(){ if (document.layers) menu.visibility="hidden"; else timer=setTimeout('menucss.visibility="hidden"',100); } function menuOver(){ if (window.timer) clearTimeout(timer); } function menuAn(e){ // Diese Funktion bestimmt jetzt den Ort des Maus-Events, // positioniert das Menü an die entsprechende Stelle und // macht es sichtbar. Außerdem wird die Anzeige des // Kontextmenüs unterdrückt. if (e && e.which != 3) { // wenn bei Netscape nicht die rechte Maustaste // gedrückt ist, abbrechen: return; } if (menucss.visibility=="show" || menucss.visibility=="visible") return; if (document.all) { menucss.left = event.clientX+document.body.scrollLeft; menucss.top = event.clientY+document.body.scrollTop; menucss.visibility = "visible"; } else { menucss.left = e.pageX; menucss.top = e.pageY; menucss.visibility = "visible"; } // Standard-Reaktion verhindern return false; } </script>
Das Ganze funktioniert natürlich nicht auf dem MacIntosh. Selbst wenn dort eine rechte Maustaste vorhanden ist können die Browser sie nicht auswerten. Der oncontextmenu-Handler hat beim Internet Explorer auf MacIntosh auch keine Wirkung.
<script> var erlaubteZeichen = "0123456789"; function pruefen(e) { if (e) var keyC = e.which; else var keyC = event.keyCode; var zeichen = String.fromCharCode(keyC); if (erlaubteZeichen.indexOf(zeichen)>-1) return true; else return false; } function init(){ document.formular.zahlEingabe.onkeydown = pruefen; } window.onload = init; </script> <form name="formular"> Bitte geben Sie eine Zahl ein: <input type="text" size="10" name="zahlEingabe"> </form>
Das funktioniert leider nicht mit Internet Explorer 4 auf MacIntosh, da dieser den Rückgabewert des Handlers ignoriert.
Wir wollen die Lösung Schritt für Schritt durchgehen. Zuerst der vorgegebene HTML-Teil:
<html> <head> <title>Eine DHTML-Textarea</title> </head>
<body>
Beweisen Sie Gödels zweites Unvollständigkeitstheorem:
<div id="rahmen" style="position:absolute; left:5px; top:40px; width:400px; height:100px; background-color:#eeeeee; layer-background-color:#eeeeee; clip:rect(0px 400px 100px 0px)"> <div id="feld" style="position:absolute; left:5px; top:2px;">|</div> </div>
Nun folgt der Script-Bereich.
<script language="JavaScript1.2" type="text/javascript">
Hier registrieren wir als Erstes einen Handler für Keypress-Events:
if (document.captureEvents) // Netscape document.captureEvents(Event.KEYPRESS) document.onkeypress = eingabe;
Keypress hat den Vorteil, dass wir uns so (anders als bei Keydown und Keyup) das Abfragen der Umschalt-Taste und die Umrechnung von Groß- und Kleinbuchstaben beim Internet Explorer sparen können.
Dann wird eine Variable angelegt, die den Inhalt der selbst gebastelten "Textarea" speichert. Im Moment ist sie noch leer:
var inhalt="";
Die Funktion schreib() überträgt den Wert dieser Variablem in das div-Element:
function schreib() { var str = inhalt+"|"; if (document.layers) { // Netscape document.rahmen.document.feld.document.write(str); document.rahmen.document.feld.document.close(); } else if (document.all) { // IE feld.innerText = str; } }
Wie Sie sehen, hängen wir hinter den Text immer noch das Zeichen "|", das als Cursor dient.
Jetzt zur Funktion eingabe(), die bei jedem Keypress-Event aufgerufen wird:
function eingabe(e) { var keyCode=(e) ? e.which : event.keyCode; if (keyCode == 8) inhalt = inhalt.substring(0, inhalt.length-1); else if (keyCode>0){ var buchstabe = String.fromCharCode(keyCode); inhalt += buchstabe; } schreib(); }
Sehen wir uns das genauer an:
In der ersten Zeile wird der Variablen keyCode der Tastatur-Code der gedrückten Taste zugewiesen: Bei Netscape erhalten wir diesen aus der Eigenschaft which des dem Handler übergebenen event-Objekts e, beim Internet Explorer aus der Eigenschaft keyCode des Objekts window.event.
Der KeyCode 8 steht für die Backspace-Taste, die das Zeichen links vom Cursor löschen soll. Deshalb wird beim Eintreffen dieses Codes inhalt mit Hilfe der substring()-Methode um einen Buchstaben verkürzt.
Wenn eine Taste gedrückt wurde, die einen KeyCode größer als 0 liefert - also nicht z.B. eine Cursor-Taste oder Shift -, dann holen wir uns mit String.fromCharCode() das dazugehörige Zeichen, hängen es hinten an die Variable inhalt und aktualisieren den Inhalt der "Textarea", indem wir die oben definierte Funktion schreib() aufrufen.
Soweit funktioniert das alles sehr schön mit Netscape. Leider reagiert onkeypress im Internet Explorer aber nicht auf die Backspace-Taste. Wir müssen also noch einen weiteren Handler einsetzen:
if (document.all) // Abfrage nur bei IE nötig document.onkeydown = sonderEingabe;
function sonderEingabe(e){ if (event.keyCode==8) { // Backspace inhalt = inhalt.substring(0, inhalt.length-1); schreib(); return false; } }
Der onkeydown-Handler feuert auch bei Backspace. Und nur wenn diese Taste gedrückt wurde, passiert etwas: Wie bereits oben für Netscape wird der Wert von inhalt um ein Zeichen verkürzt und ins div-Element geschrieben. return false blockiert die "Back"-Funktion.
Damit ist das Formularfeld auf beiden Browsern funktionsfähig, und wir können den Skript-Bereich beenden.
</script> </body> </html>
Im Internet Explorer gibt es übrigens seit Version 5.5 das Attribut contentEditable für praktisch alle HTML-Elemente. Damit können Sie sehr einfach beliebige Teile einer Webseite durch die Besucher verändern lassen. Sie müssen dazu nur der Eigenschaft contentEditable des Elements den Wert true zuweisen.
Zuerst die Seite mit dem einfachen Drag&Drop:
<html> <head> <title>Drag'n'Drop Test</title> <script language="JavaScript1.2" type="text/javascript"> <!-- // Die globale Variable drag sagt uns, ob gerade ein div // "aktiviert"ist oder nicht: drag = "inaktiv"; // Die nächsten beiden Variablen speichern die zuletzt // gemessene Mausposition: aktivX = 0; aktivY = 0; function init() { // eine Cross-Browser-Referenz auf das Test-Div: testDiv = (document.layers) ? document.test : (document.all) ? window.test : document.getElementById("test"); // Jetzt fangen wir die Events ab: if (testDiv.captureEvents) { // Netscape 4 testDiv.captureEvents(Event.MOUSEDOWN); document.captureEvents(Event.MOUSEMOVE | Event.MOUSEUP); } testDiv.onmousedown = mdown; document.onmouseup = mup; document.onmousemove = mmove; } // init-Funktion onload aufrufen: window.onload = init; function mdown(e){ // Der MouseDown-Handler setzt die Variable drag auf // "aktiv" und bestimmt die aktuelle Mausposition. drag = "aktiv"; aktivX = (e) ? e.pageX : event.clientX + document.body.scrollLeft; aktivY = (e) ? e.pageY : event.clientY + document.body.scrollTop; // Damit auf dem MacIntosh nicht das Kontextmenü // erscheint, sollte man onmousedown immer false // zurückgeben: return false; } function mmove(e){ // Im MouseMove-Handler passiert nur etwas, wenn drag auf // "aktiv" steht: if (drag == "inaktiv") return; // Jetzt wird die neue Mausposition gemessen: var neuX = (e) ? e.pageX : event.clientX + document.body.scrollLeft; var neuY = (e) ? e.pageY : event.clientY + document.body.scrollTop; // ...der Abstand zur vorherigen Position berechnet: var distX = (neuX-aktivX); var distY = (neuY-aktivY); // ...die neue Position in den globalen Variablen // gespeichert: aktivX = neuX; aktivY = neuY; // ...und schließlich das Test-Div um die berechnete // Distanz verschoben: if (document.layers) { // Netscape 4 testDiv.left += distX; testDiv.top += distY; } else { testDiv.style.left = parseInt(testDiv.style.left) + distX; testDiv.style.top = parseInt(testDiv.style.top) + distY; } } function mup(e){ // onmouseup setzen wir einfach drag auf "inaktiv": drag = "inaktiv"; } //--> </script> </head> <body> <!-- das Test-Element --> <div id="test" style="position:absolute; left:20px; top:20px; width:100px; height:100px; clip:rect(0px 100px 100px 0px); background-color:green; layer-background-color:green;"> test </div> </body> </html>
Vielleicht fallen Ihnen diese Zeilen in der init()-Funktion auf:
if (testDiv.captureEvents) { // Netscape 4 testDiv.captureEvents(Event.MOUSEDOWN); document.captureEvents(Event.MOUSEMOVE | Event.MOUSEUP); } testDiv.onmousedown = mdown; testDiv.onmouseup = mup; document.onmousemove = mmove;
Anders als in den vorherigen Übungen setze ich captureEvents() nur bei Netscape 4, nicht aber bei Netscape 6 ein. Warum? Weil captureEvents() ausschließlich eine Methode von window-, document- und layer-Objekten ist. testDiv wird von Netscape 4 als Layer angesehen. Netscape 6 kennt aber keine Layer, weshalb er beim Versuch, auf testDiv die Methode captureEvents() anzuwenden, einen Fehler bringen würde.
Man kann auf captureEvents() für Netscape 6 ruhig verzichten, weil dieser wie Internet Explorer Event-Bubbling unterstützt.
Wenn Sie Schwierigkeiten beim "einfachen" Drag&Drop hatten, dann kopieren Sie sich die Lösung und versuchen Sie jetzt, sie so weiter zu entwickeln, dass sie universell einsetzbar wird. (Auf der CD finden Sie das oben abgedruckte Skript im Verzeichnis "sonstiges" als "dragndrop.html").
Hier ist die fertige Lösung:
<script language="JavaScript1.2" type="text/javascript"> <!-- drag = "inaktiv"; aktivX = 0; aktivY = 0; // Wir brauchen eine neue globale Variable, die das derzeit // aktive Element angibt: aktivDiv = null; // Ausserdem halten wir den z-index-Wert des derzeit aktiven // Elements fest, damit wir das nächste aktive Element // darüber legen können. Am Anfang setzen wir ihn einfach // auf 1000: aktivZindex = 1000; function init() { // Im Array divs[] sammeln wir alle (positionierten) div- // Elemente der Seite: divs = (document.layers) ? document.layers : (document.all) ? document.all.tags("DIV") : document.getElementsByTagName("DIV"); // Jetzt weisen wir jedem div den MouseDown-Handler zu: for (i=0; i<divs.length; i++) { if (divs[i].captureEvents) // Netscape 4 divs[i].captureEvents(Event.MOUSEDOWN); divs[i].onmousedown = mdown; } // die andern Handler nicht vergessen: if (document.captureEvents) // Netscape document.captureEvents(Event.MOUSEMOVE | Event.MOUSEUP); document.onmouseup = mup; document.onmousemove = mmove; } window.onload = init; function mdown(e){ // Wir müssen onmousedown herausfinden, welches div- // Element aktiviert wurde. Dazu benutzen wir "this". // Dann zählen wir den bislang höchsten z-Index-Wert eins // nach oben und weisen ihn dem aktiven div zu. Der Rest // ist wie gehabt. aktivDiv = this; if (document.layers) aktivDiv.zIndex = aktivZindex++; else aktivDiv.style.zIndex = aktivZindex++; drag = "aktiv"; aktivX = (e) ? e.pageX : event.clientX + document.body.scrollLeft; aktivY = (e) ? e.pageY : event.clientY + document.body.scrollTop; return false; } function mmove(e){ // fast keine Änderungen im Vergleich zum einfachen // Drag&Drop if (drag == "inaktiv") return; var neuX = (e) ? e.pageX : event.clientX + document.body.scrollLeft; var neuY = (e) ? e.pageY : event.clientY + document.body.scrollTop; var distX = (neuX-aktivX); var distY = (neuY-aktivY); aktivX = neuX; aktivY = neuY; if (document.layers) { aktivDiv.left += distX; aktivDiv.top += distY; } else { aktivDiv.style.left = parseInt(aktivDiv.style.left) + distX; aktivDiv.style.top = parseInt(aktivDiv.style.top) + distY; } } function mup(e){ drag = "inaktiv"; } //--> </script>
Was noch fehlt, ist die Speicherung des Layouts in einem Cookie. Mit Hilfe der Cookie-Funktionen aus Kapitel 8 ist das gar nicht so schwierig.
Zuerst ergänzen wir die Funktion mup() so, dass bei jedem Mouseup-Event die aktuellen div-Positionen in den Cookie geschrieben werden. Dazu gehen wir den Array divs[]durch, fragen die Koordinaten ab und hängen sie zusammen an einen String. Aus diesem String müssen wir später die Koordinaten wieder rekonstruieren können, deshalb stellen wir zwischen zwei zusammengehörige x/y-Werte jeweils ein Komma und trennen sie vom nächsten Wertpaar mit einem Plus-Zeichen:
function mup(e){ drag = "inaktiv"; // Cookie-String zusammenstellen: var cookieString = ""; for (i=0; i<divs.length; i++) { var posX = (document.layers) ? divs[i].left : parseInt(divs[i].style.left); var posY = (document.layers) ? divs[i].top : parseInt(divs[i].style.top); cookieString += posX + "," + posY + "+"; } // Verfallspunkt in 100 Tagen: var jetzt = new Date(); var verfall = new Date(jetzt.getTime() + 1000*60*60*24*100); // Cookie schreiben: schreibCookie("Layout", cookieString, verfall); }
In der init()-Funktion lesen wir den Cookie aus und setzen alle Elemente an ihren dort gespeicherten Platz:
function init() { ... bisherige Anweisungen ... // Cookie lesen: var cookieString = liesCookie("Layout"); if (cookieString) { // Array mit Wertpaaren erzeugen: var divPosArray = cookieString.split("+"); for (i=0; i<divs.length; i++) { // Werte aus Wertpaar lesen: var posX = divPosArray[i].split(",")[0]; var posY = divPosArray[i].split(",")[1]; // Elemente positionieren: if (document.layers) { divs[i].left = posX; divs[i].top = posY; } else { divs[i].style.left = posX; divs[i].style.top = posY; } } } }
Sie müssen ein Image-Objekt in Ihre Seite einbinden, dessen src-Atttribut auf ein Bild verweist, das auf dem Server liegt, den Sie testen wollen. Wenn der Error-Handler feuert, konnte das Bild nicht geladen werden - der Server ist nicht erreichbar.
Das Image-Objekt kann entweder in HTML oder per JavaScript erzeugt werden. Die erste Version sieht etwa so aus:
<img src="http://zu.testender.server.de/einBild.jpg" onerror="alert('Server nicht erreichbar!')" onload="alert('Alles klar.')">
Die andere Lösung benötigt kein sichtbares Bild:
<script> function bildErrorHandler(e) { alert("Server nicht erreichbar!"); } function bildLoadHandler(e) { alert("Alles klar."); } testBild = new Image(); testBild.onerror = bildErrorHandler; testBild.onload = bildLoadHandler; testBild.src = "http://zu.testender.server.de/einBild.jpg"; </script>
Das Bild einBild.jpg muss natürlich auf dem Testserver vorhanden sein, damit der Error-Event nicht immer eintritt.
// Error-Handler definieren function errorHandler() { // Error-Handler wieder abschalten window.onerror = keinErrorHandling; window.status = "Fehler!"; weiter(); return true; } function keinErrorHandling(){ return false; }
// Error-Handler registrieren window.onerror = errorHandler; // riskante Anweisung riskanteAnweisung();
// nichts passiert? Dann geht es hier weiter window.onerror = keinErrorHandling; weiter();
Wenn kein Fehler eintritt, wird der Skriptblock einfach von oben nach unten abgearbeitet, wo nach riskanteAnweisug() weiter() aufgerufen wird. Im Fehlerfall bricht die Abarbeitung bei riskanteAnweisung() ab, window.onerror feuert, und die Funktion errorHandler() wird gestartet. Diese schreibt "Fehler!" in die Statuszeile und ruft ihrerseits weiter() auf.
Eine hübsche Alternative besteht darin, für den Aufruf von weiter() einen neuen JavaScript-Block zu erzeugen. Dazu können Sie setTimeout() verwenden:
function errorHandler() { window.status = "Fehler!"; window.onerror = keinErrorHandling; return true; } window.onerror = errorHandler; window.setTimeout("weiter()", 0); riskanteAnweisung(); window.onerror = keinErrorHandling;
Das funktioniert, weil bei einem JavaScript-Error nur der aktuelle Skriptblock abgebrochen wird. Die setTimeout-Anweisung erzeugt aber intern einen neuen Block. Der zweite Parameter bei setTimeout gibt an, wann dieser Block ausgeführt werden soll. Ich habe 0 Millisekunden eingetragen. Das bedeutet aber nicht, dass sofort mit der Abarbeitung des neuen Blocks begonnen wird. Im Allgemeinen verarbeiten die Browser JavaScript-Blöcke immer der Reihe nach. Das heißt, es wird zuerst der aktuelle Block fertig ausgeführt, dann erst wird auf die Uhr geschaut und nachgesehen, ob irgendwelche anderen Blöcke sich für diese Zeit angemeldet haben.
Sehen wir uns an, was im Listing passiert, wenn ein Fehler eintritt. Zuerst wird ein Error-Handler definiert und registriert, dann der neue Skriptblock erstellt und für "sofort" zur Abarbeitung bestimmt:
window.setTimeout("weiter()", 0);
Im Moment interessiert das den Browser aber nicht, denn zuerst muss ja noch der gegenwärtige Block beendet werden. Als Nächstes kommt:
riskanteAnweisung();
und dabei passiert ein Fehler. Der Error-Handler springt an, schreibt "Fehler!" in die Statuszeile und unterdrückt die Fehlermeldung des Browsers. Der Skriptblock, in dem der Fehler auftrat, wird abgebrochen.
Jetzt schaut der Browser auf die Uhr, sieht, dass er bereits vor einigen Millisekunden einen neuen Skriptblock hätte starten sollen, und beeilt sich, das nachzuholen. Der neue Block enthält nur eine einzige Anweisung, nämlich weiter(), die nun ausgeführt wird.
function errorHandler(meldung, url, zeile) { str = '<h2>Ohje, ein JavaScript-Fehler!</h2>'; str+= '<form action="/cgi-bin/mailer.pl" method="post">'; str+= 'Fehlermeldung: '; str+= '<textarea name="meldung" rows="60" cols="3">'; str+= meldung; str+= '</textarea>'; str+= '<br> URL: '; str+= '<input type="text" name="url" size="60" '; str+= 'value="'+url+'">'; str+= '<br> Zeile: '; str+= '<input type="text" name="zeile" size="5" '; str+= 'value="'+zeile+'">'; str+= '<br>Browser: '; str+= '<input type="text" name="browser" size="60" '; str+= 'value="'+navigator.appVersion+'">'; str+= '<br><br>'; str+= 'Bitte klicken Sie hier, um den Webmaster zu'; str+= 'benachrichtigen!'; str+= '<br><input type="submit">'; str+= '</form>'; var neuesFenster = window.open("","Fehlerfenster"); neuesFenster.document.write(str); neuesFenster.document.close(); return true; } window.onerror = errorHandler;
Bei der MacIntosh-Ausführung von Internet Explorer 4 werden die Fehler-Informationen nicht als Parameter übergeben. Sie erhalten deshalb bei diesem Browser jeweils nur "undefined".
Die Lösung, die natürlich in einer einzigen Zeile stehen muss, ist:
javascript:void(document.onmousedown=function(e)
{var ziel=e?e.target:event.srcElement;ziel.parentNode.removeChild(ziel);})
Etwas übersichtlicher formatiert und weniger kompakt lauten die Anweisungen:
function mDownHandler(e) { var ziel=(e) ? e.target : event.srcElement; ziel.parentNode.removeChild(ziel); } document.onmousedown = mDownHandler;
Ich benutze onmousedown anstatt onclick, damit das Ziel-Element keine Gelegenheit bekommt, vor seiner Zerstörung noch auf den Klick zu reagieren, um zum Beispiel ein Popup-Fenster zu öffnen.