7. Event Handling

Eine der grossen Stärken von DHTML ist die Interaktionsfähigkeit, d.h. die Möglichkeit auf den User zu reagieren. Realisiert wird dies durch sogenannte Ereignisüberwachung. Die bekannteste Methode ein Ereignis zu überwachen ist der traditionelle Eventhandler, der zuerst im Navigator 2 eingeführt wurde. Dazu schreibt man einfach den Eventhandler als Attribut in den HTML-Tag:


   <a href="#" onmouseover="window.status='Calvin & Hobbes';return true;">
   Calvin & Hobbes
   </a>

Nicht jeder Tag kann auf jedes Ereignis reagieren. Im Navigator 3 wurde die Möglichkeit eingeführt einem Objekt über einen Eventhandler eine Funktion zuzuweisen. Die allgemeine Syntax für solch eine Operation ist object.eventhandler = function;.
Ein Beispiel:


   <script>
   function message() {alert("Guten Morgen!")}
   window.onload=message;
   </script>

Welche Objekte auf welche Ereignisse reagieren können wurde in der Definition von JavaScript durch Netscape festgesetzt. Zu beachten: der Eventhandler muss vollständig klein geschrieben werden.
Und: die Funktion wird ohne Klammern und ohne Hochkomma geschrieben.

Ab Version 4 bieten nun Netscape und Microsoft eine neue Art Ereignisse zu überwachen. Man kann nun Ereignisse generell im Dokument (oder Layer) überwachen und sogar abfangen bevor sie ihr eigentliches Ziel erreichen. So könnte es sein, dass man nach einem bestimmten Ereignis jeden click auf der Seite unterbinden will, egal wo der passiert. Im NS4 muss man die Fähigkeit zur Überwachung explizit initiieren; nicht so in IE und NS6.


   <script>
   function message()
	{alert("You double-clicked somewhere in the document!")}
   if(NS4) {document.captureEvents(Event.DBLCLICK);}
   document.ondblclick=message;
   </script>

In der DOM-Specifikation des W3C, und in Mozilla und Netscape 6 implementiert, gibt es eine weitere Möglichkeit: man kann jedem Element einen Eventhandler zuweisen bzw entfernen, und zwar zu jeder Zeit. Die Methode heisst addEventListener.

Beispiel

Quellcode:

   <div id="D1"><p id="P1"><a id="A1" href="http://www.google.com">Google</a></p></div>
   <script>
   
   function message(clickEvent)
   	{
	if(clickEvent.eventPhase==1) {phase="capture";}
	else if(clickEvent.eventPhase==2) {phase="umkehr";}
	else if(clickEvent.eventPhase==3) {phase="bubbling";}
	else {phase=clickEvent.eventPhase}
	
	str = "Eigentliches Ziel: " + clickEvent.target+"\n";
	str+= "Derzeitiger Standort: " + clickEvent.currentTarget+"\n";
	str+= "Event Phase: " + phase;
	
	clickEvent.preventDefault()
	
	alert(str)
	}
   
   document.getElementById("A1").addEventListener("click",message,true);
   document.getElementById("P1").addEventListener("click",message,true);
   document.getElementById("D1").addEventListener("click",message,true);
   document.documentElement.addEventListener("click",message,true);
   
   document.getElementById("A1").firstChild.addEventListener("click",message,true);

   document.getElementById("A1").addEventListener("click",message,false);
   document.getElementById("P1").addEventListener("click",message,false);
   document.getElementById("D1").addEventListener("click",message,false);
   document.documentElement.addEventListener("click",message,false);
   
   </script>

Der dritte Wert in der Funktion addEventListener kontrolliert das Event-Bubbling, eine etwas tiefere Materie des Event-Handlings. Um dies zu verstehen muss man sich zuerst die hierarchische Struktur eines HTML-Dokuments vor Augen führen, das heisst dass die Elemente ineinander geschachtelt sind.

Ich möchte hier das Event-Modell von Mozilla/Netscape 6 erklären, da dies der einzige Browser ist, der die Methode addEventListener vernünftig interpretiert. Hat man nun etwa einen Link in einem Paragraphen und dieser liegt in einem div-Tag, so wird eigentlich ein click auf den Link in jedem dieser Elemente ein Event auslösen.
Bei Mozilla läuft das nun so ab: bei einem click auf den Link wird das Event zuerst im document-root ausgelöst und dann in der Hierarchie des Dokuments bis zum eigentlichen Ziel, nämlich dem Element wo das Ereignis tatsächlich stattfand, durchgereicht. In unserem Fall also über das div-Element, den Paragraphen und den Link zum Textnode.
Man nennt diese Phase des Ereignislaufs die Capture-Phase.
Beim eigentlichen Ziel kehrt das Event um und durchläuft die Hierarchie in umgekehrter Reihenfolge noch einmal, dies ist die Bubbling-Phase, bis es im document-root anlangt und dort die vorgesehene Reaktion auslöst, in unserem Falle wird eine neue Seite geladen.
Mittels addEventListener kann man nun in den Lauf des Ereignisses eingreifen. Die Parameter geben an, welches Ereignis mit welcher Funktion verarbeitet werden soll, und in welcher Phase: true steht für die Capture-Phase, false für die Bubbling-Phase.
Zusätzlich kann man per preventDefault() die vorgesehene Reaktion verhindern und per cancelBubble=true den Lauf des Ereignisses stoppen.

Nicht immer wird es notwendig sein auf drei verschiedene Arten Ereignisverarbeitung zu betreiben, da Mozilla durchaus auch die "alten" Methoden beherrscht. Folgendes Beispiel funktioniert jedenfalls auch in Mozilla, ohne dass dabei die Methode addEventListener zum Einsatz käme.

Beispiel

Quellcode:

   <html>
   <head><title>bewegbares Bild</title></head>

   <body bgcolor="#00004b" scroll=no text="white">
   Dieses Bild lässt sich mit der Maus bewegen:<br>
   <div id="mD" name="mD" style="position:absolute;left:100px;top:100px;">
   <img name="sensor" src="img/hobbes.gif" border=0 width=32 height=32>
   </div>

   <script language="JavaScript" type="text/javascript">
   var moveOn=false;
   var startXmouse=0;var startYmouse=0;
   var startXlayer=0;var startYlayer=0;
   var mouseX=0;var mouseY=0;
   var d=document;
   if(d.all) {var theDiv=d.all["mD"].style}
   else if(d.layers) {var theDiv=d.layers["mD"]}
   else {var theDiv=d.getElementById("mD").style}

   function getMousePos(moveEvent)
	{
	mouseX=(window.event)? window.event.x : moveEvent.pageX;
	mouseY=(window.event)? window.event.y : moveEvent.pageY;
	}

   function moveLayer()
	{
	if(moveOn)
		{
		theDiv.left=startXlayer+mouseX-startXmouse;
		theDiv.top=startYlayer+mouseY-startYmouse;
		}
	setTimeout("moveLayer()",40)
	}

   function startMove(downEvent)
	{
	eventObj=(window.event)?  window.event.srcElement : downEvent.target;
	if(eventObj.name && eventObj.name=="sensor")
		{
		startXmouse=(window.event)? window.event.x : downEvent.pageX;
		startYmouse=(window.event)? window.event.y : downEvent.pageY;
		startXlayer=parseInt(theDiv.left);
		startYlayer=parseInt(theDiv.top);
		moveOn=true;
		return false;
		}
	}

   if(document.captureEvents)
	{document.captureEvents(Event.MOUSEMOVE|Event.MOUSEDOWN|Event.MOUSEUP)}

   document.onmousemove=getMousePos;
   document.onmousedown=startMove;
   document.onmouseup=Function("moveOn=false;");
   document.ondragstart=Function("return false;");

   window.onload=moveLayer;
   </script>
   </body>
   </html>

Die Unterschiede der Ereignis- Modelle und die Eigenschaften des Event-Objekts lassen sich in den folgenden drei Artikeln schön nachlesen: The Amazing Event Model Part I und Part II sowie Column 74 von Doc JavaScript. Die Eigenschaften und Methoden sind auch in Kapitel 8 zusammengefasst.

Natürlich lassen sich auch Tastaturevents verarbeiten, wobei man hier sehr auf die Betriebssysteme achten muss. Soweit ich weiss lassen sich Tastatur-Ereignisse auf Linux-Systemen nicht verarbeiten. Ein nettes Beispiel, das die Anwendung von Tastatur-Abfragen zeigt, ist das gute alte Sokoban, das ich in DHTML nachprogrammiert habe.

Event Handling ist sicherlich der Teil von DHTML, der am schwierigsten crossbrowser-konform zu programmieren ist. Meist geht man von Projekt zu Projekt unterschiedlich vor.