19.7 DHTML und DOM
Als 1998 die 4er-Versionen der beiden damals wichtigsten Browser eingeführt wurden, war das Schlagwort Dynamic HTML (DHTML) eine der Lieblingsvokabeln aller Webdesigner. Für einen Begriff, der gar keine einheitliche Technologie beschreibt, ist das bemerkenswert: Die Bezeichnung DHTML entstand vor über dreizehn Jahren in den Marketingabteilungen von Microsoft und Netscape. Es handelt sich um die Zusammenarbeit zwischen Stylesheets zur Formatierung von HTML-Inhalten und neueren JavaScript-Fähigkeiten.
Insbesondere wurden in den 4er-Browsern zum ersten Mal sogenannte Objektmodelle eingeführt, die es ermöglichen, einen Großteil der Elemente in HTML-Dokumenten anzusprechen und auch nach dem Laden des Dokuments noch dynamisch zu verändern. Die diversen proprietären Modelle sind glücklicherweise veraltet; praxisrelevant ist inzwischen nur noch das Document Object Model (DOM) des W3C. Es ermöglicht konsequent die nachträgliche Änderung jedes beliebigen Elements eines HTML-Dokuments, indem es das Dokument als hierarchisch verschachtelte Baumstruktur versteht. Das DOM ist nicht nur für JavaScript und HTML gedacht, sondern wurde in vielen verschiedenen Programmiersprachen für den Zugriff auf XML-Dokumente aller Art implementiert. In Kapitel 15, »XML«, wird beispielsweise die Verwendung des DOM in Java angesprochen.
DOM wird von folgenden Browsern interpretiert: Internet Explorer ab 5.0, Firefox ab 1.0, Netscape ab 6.0, Mozilla und Opera ab 6.0, Safari ab 1.0.
19.7.1 W3C-DOM im Überblick
Mithilfe von DOM können Sie auf jedes einzelne Element einer Webseite zugreifen. Dazu wird das Objekt als Baummodell aus verschiedenen Arten von Knoten betrachtet. Jeder Knoten kann beliebig viele Kindknoten besitzen. Jeder Knoten besitzt einen der in gezeigten Knotentypen, der über seine Eigenschaft nodeType abgefragt werden kann:
Knotentyp | Bedeutung |
1 |
Element (HTML-Tag) |
2 |
Attribut (funktioniert so nicht!) |
3 |
einfacher Text |
8 |
HTML-Kommentar |
9 |
das Dokument selbst |
Es existieren zwei verschiedene Möglichkeiten, auf einen Knoten vom Typ HTML-Tag zuzugreifen:
- Die Methode
liefert eine Referenz auf das Tag zurück, dem über das Attribut id eine spezielle ID zugewiesen wurde. Beispielsweise können Sie auf einen Absatz, der so definiert wurde:
<p id="test">Spezieller Absatz, Marke test</p>
mithilfe dieser DOM-Methode zugreifen:
document.getElementById("test")
- Mithilfe der Methode
erhalten Sie eine Referenz auf ein Array aller Elemente, die dem angegebenen HTML-Tag entsprechen. Beispielsweise entspricht der folgende Ausdruck dem dritten <p>-Tag einer Seite:
document.getElementsByTagName("p")[2]
Auf Text- und Kommentarknoten können Sie nicht direkt zugreifen. Sie sind stets Kindknoten der umschließenden HTML-Tags. Auf die Kindknoten eines Elements sowie auf seinen Elternknoten und seine »Geschwister« können Sie mithilfe der in Tabelle 19.2 gezeigten Eigenschaften zugreifen:
HTML-Tag-Knoten besitzen die Eigenschaft nodeName, die den Namen des eigentlichen HTML-Tags enthält. Text- und Kommentarknoten weisen dagegen die Eigenschaft nodeValue auf, die den Textinhalt enthält. nodeValue liefert möglicherweise etwas anderes zurück, als Sie erwarten. Betrachten Sie beispielsweise den folgenden Auszug aus dem Body eines HTML-Dokuments:
<p id="test">Dies ist der <i>alte</i> Text.</p>
<script language="JavaScript" type="text/javascript">
<!--
alert (document.getElementById ("test")
.firstChild.nodeValue);
//-->
</script>
Die alert()-Anweisung greift zunächst über die Methode getElementById() auf den Absatz mit der ID test zu. Anschließend liest sie per nodeValue den Textinhalt des ersten Kindknotens (firstChild) des aktuellen Elements. Vielleicht überrascht es Sie, zu hören, dass das Ergebnis nicht so lautet:
Dies ist der alte Text
Vielmehr bekommen Sie lediglich Folgendes zu sehen:
Dies ist der
Der Text »Dies ist der« bildet den ersten Kindknoten des Absatzes, das HTML-Tag <i>...</i> ist der zweite und der restliche »Text« der letzte.
Das nächste Beispiel verwendet einige dieser Methoden und Eigenschaften zur Anzeige der aktuellen Uhrzeit im Fließtext eines Absatzes. Der Absatz selbst wird so definiert:
<p id="uhr">Uhrzeit</p>
Im Head steht folgende Funktion, die im <body>-Tag im Event Handler onload aufgerufen werden sollte:
function zeit () {
var jetzt = new Date();
var std = jetzt.getHours();
var min = jetzt.getMinutes();
var sek = jetzt.getSeconds();
var zeitangabe = std < 10 ? "0" : "";
zeitangabe += std + ":";
zeitangabe += min < 10 ? "0" : "";
zeitangabe += min + ":";
zeitangabe += sek < 10 ? "0" : "";
zeitangabe += sek;
document.getElementById ("uhr").firstChild.
nodeValue = "Es ist " + zeitangabe + " Uhr";
setTimeout ("zeit();", 1000);
}
Das Thema Datums- und Uhrzeitanzeige wurde bereits in diesem Kapitel behandelt. Neu ist hier lediglich die Zeile
document.getElementById ("uhr").firstChild.
nodeValue = "Es ist " + zeitangabe + " Uhr";
Über getElementById() wird der Absatz mit der ID uhr angesprochen. Dessen erster Kindknoten firstChild ist der Absatztext, der mithilfe einer Wertzuweisung an seine Eigenschaft nodeValue geändert wird.
19.7.2 Eine DOM-Baum-Anzeige
Das folgende Beispiel durchwandert rekursiv den DOM-Baum des aktuellen Dokuments und gibt in einem separaten Fenster Informationen über alle Knoten aus, die es dabei findet. Abbildung 1.1 zeigt das Skript bei der Arbeit.
Hier zunächst der Quellcode des gesamten HTML-Dokuments:
<html>
<head>
<title>DOM-Baumdiagramm</title>
<script language="JavaScript" type="text/javascript">
<!--
var infofenster;
function initDOMTree() {
infofenster =
open("", "", "width=400,height=400");
showDOMTree(document, 0);
}
function showDOMTree(knoten, indentation) {
var typ = knoten.nodeType;
var typtext, info;
switch (typ) {
case 1:
typtext = "HTML-Tag";
info = knoten.nodeName;
break;
case 3:
typtext = "Text";
info = knoten.nodeValue;
break;
case 8:
typtext = "Kommentar";
info = knoten.nodeValue;
break;
case 9:
typtext = "Dokument";
info = "Das ganze HTML-Dokument";
break;
default:
typtext = "Anderer Typ";
info = "XML-Dokument?";
}
// Einrücken
for (var i = 0; i < indentation; i++) {
infofenster.document.write
(" ");
}
infofenster.document.write("<b>" + typtext
+ "</b> (<i>" + info + "</i>)<br />");
// Kinder rekursiv bearbeiten
if (knoten.hasChildNodes()) {
for (var j = 0;
j < knoten.childNodes.length; j++) {
showDOMTree(knoten.childNodes[j],
indentation + 1);
}
}
}
//-->
</script>
</head>
<body onload="initDOMTree();">
<!-- Jetzt geht's los! -->
<font size="4" color="#FF0000">Hier sehen Sie
<i>die DOM-Baumstruktur <b>des aktuellen
<u>Dokuments</u></b></i>.</font>
</body>
</html>
Abbildung 19.1 Die DOM-Baumstrukturanalyse in Aktion
Im Grunde verwendet das Skript nur Funktionen, die bereits besprochen wurden, und benötigt deshalb nicht viele Erläuterungen.
Für die eigentliche Rekursion wird mithilfe der Methode knoten.hasChildNodes() überprüft, ob überhaupt Kindknoten vorhanden sind. Ist dies der Fall, werden sie in einer Schleife über alle Elemente des Arrays knoten.childNodes[] durchlaufen. Für jedes Kindelement wird wiederum die Funktion selbst aufgerufen; dabei wird der um 1 erhöhte Wert der Variablen indentation übergeben, um jeweils die korrekte Einrückung vorzunehmen.
Beachten Sie zuletzt, dass die explizite Deklaration der Schleifenzähler i und j mithilfe von var hier absolut notwendig ist, weil sie ansonsten als globale Variablen betrachtet würden und so bei der Rekursion die falschen Werte hätten.
19.7.3 DOM-Anwendung in der Praxis
Das wichtigste Anwendungsgebiet von DOM ist es, nachträglich Veränderungen an Struktur und Inhalt des Dokuments vorzunehmen. Am häufigsten wird es verwendet, um die Positionierung und andere per Stylesheet definierte Eigenschaften von Layern zu ändern. Die bereits in Kapitel 17, »Webseitenerstellung mit (X)HTML«, vorgestellten Layer sind frei schwebende <div>-Elemente, die über das CSS-Attribut position an eine bestimmte Stelle gesetzt werden. Die festgelegte Position kann nachträglich geändert werden, um Animationen zu erzeugen. Abgesehen davon können Sie auch jede andere CSS-Eigenschaft ändern, beispielsweise Farben, Schriftformatierungen, die generelle Sichtbarkeit oder die Stapelreihenfolge.
Über die DOM-Eigenschaft style können Sie auf die Stylesheet-Formatierungen von Layern (und beliebigen anderen HTML-Elementen) zugreifen und diese dynamisch ändern. Dabei besitzt style jeweils Untereigenschaften, deren Namen mit den Original-CSS-Attributen übereinstimmen. So können Sie etwa über top und left die Position eines absolut positionierten Layers ändern oder mithilfe von color die Schriftfarbe modifizieren. Die einzige Besonderheit gilt für diejenigen Attribute, deren CSS-Name einen Bindestrich enthält: Statt dieses Sonderzeichens wird in üblicher JavaScript-Bezeichner-Konvention der darauffolgende Buchstabe großgeschrieben – aus background-color wird beispielsweise backgroundColor; text-align wird zu textAlign.
Die Werte für die jeweiligen Stil-Eigenschaften sind Strings, deren Inhalt auf dieselbe Weise festgelegt wird wie bei Stylesheet-Angaben. Betrachten Sie zum Beispiel den folgenden Absatz:
<p id="info">Der Hintergrund dieses Absatzes kann gelb werden!</p>
Mithilfe der folgenden JavaScript-Anweisung können Sie den Hintergrund wie versprochen gelb einfärben:
document.getElementById("info").style
.backgroundColor = "#FFFF00";
Interessant ist in diesem Zusammenhang, dass neuere Browser Event Handler wie onmouseover oder onmouseout für beinahe jedes Element unterstützen. So ist es zum Beispiel inzwischen weit verbreitet, in umfangreichen Tabellen die Zeile oder Zelle, in der sich der Cursor gerade befindet, durch Änderung der Hintergrundfarbe hervorzuheben. Die folgende Funktion kann durch einen solchen Handler aufgerufen werden, um die Farbänderung durchzuführen:
function betonen(id, farbe) {
document.getElementById (id).
style.backgroundColor = farbe;
}
Hier sehen Sie eine Tabellenzeile, die bei Mausberührung mithilfe dieser Funktion ihre eigene Hintergrundfarbe ändert:
<tr id="zeile" style="background-color: #FFFF00"
onmouseover="betonen ('zeile', '#FFFF99');"
onmouseout="betonen ('zeile', '#FFFF99');">
<td>DOM</td>
<td>IE 4.0-Objektmodell</td>
<td>Netscape-Objektmodell</td>
</tr>
Die Manipulation der Eigenschaften von Layer-Objekten funktioniert im Prinzip genauso. Denken Sie daran, dass ein <div>-Element nur dann zum echten Layer wird, wenn es durch das CSS-Attribut position auf eine feste Position gesetzt wird. Da sich HTML-Tag-Knoten am leichtesten über ihre ID ansprechen lassen, liegt es nahe, die CSS-Formatierung für den Layer in einer unabhängigen Stilangabe vorzunehmen, die dem Layer dann gleichzeitig mit seiner ID zugewiesen wird.
Das folgende Beispiel lässt nach einer Wartezeit von drei Sekunden nach dem Laden einen Layer mit einem Bild von links in den sichtbaren Bereich des Fensters fahren; anschließend bleibt er fünf Sekunden stehen und wird schließlich ausgeblendet. Auf immer mehr Websites ist heute Werbung nach diesem Schema zu sehen. Hier das Listing:
<html>
<head>
<title>Aufdringliche Werbung</title>
<style type="text/css">
<!--
#werbung {
position: absolute;
top: 100px;
left: -200px
}
-->
</style>
<script language="JavaScript"
type="text/javascript">
<!--
// Aktuelle Position
var x = -200;
function werbungZeigen() {
x += 5;
document.getElementById ("werbung").style.
left = x + "px";
if (x >= 100)
setTimeout ("werbungSchliessen ();",
5000);
else
setTimeout ("werbungZeigen ();", 50);
}
function werbungSchliessen() {
document.getElementById ("werbung")
.style.visibility = "hidden";
}
setTimeout ("werbungZeigen();", 3000);
//-->
</script>
</head>
<body>
<div id="werbung"><img src="werbung.gif"
width="198" height="198"></div>
... beliebiger Inhalt ...
</body>
</html>
19.7.4 Dokumentinhalte verändern und austauschen
Die Struktur des DOM-Baums, den ein HTML-Dokument bildet, kann beliebig manipuliert werden, um Inhalte vollständig gegen andere auszutauschen. Zu diesem Zweck sind Knotenobjekte mit einer Reihe von Methoden ausgestattet, die entsprechende Manipulationen ermöglichen. Tabelle 19.3 zeigt hierzu eine Übersicht.
Das folgende Listing tauscht den Inhalt eines vollständigen Absatzes aus, der aus mehreren Text- und Elementknoten besteht:
<html>
<head>
<title>Eine Geschichte in zwei Teilen</title>
<script language="JavaScript"
type="text/javascript">
<!--
function weiter() {
var k1 = document.createTextNode
("Hier folgt der zweite Teil des ");
var k2 = document.createElement ("b");
var k2a = document.createTextNode
("kurzen");
k2.appendChild (k2a);
var k3 = document.createTextNode
(" Textes.");
document.getElementById ("story")
.replaceChild (k1, document.
getElementById ("story").firstChild);
document.getElementById ("story")
.appendChild (k2);
document.getElementById ("story")
.appendChild (k3);
}
//-->
</script>
</head>
<body>
<div id="story"><p>Dies ist ein <i>kurzer</i>
Text. Er besteht aus zwei Teilen. Den zweiten Teil
können Sie durch Klick auf den Link
"Weiter" lesen.</p></div>
<p><a href="javascript:weiter();">Weiter</a></p>
</body>
</html>
Da von Anfang an bekannt ist, dass der komplette Inhalt des <div>-Elements mit der ID story ausgetauscht werden soll, wurde dieser insgesamt zwischen die Tags <p> und </p> gepackt. Eine mögliche Alternative bestünde darin, sämtliche Kindknoten von story mithilfe einer Schleife zu entfernen:
while (document.getElementById ("story")
.hasChildNodes()) {
document.getElementById ("story").removeChild
(document.getElementById ("story").firstChild);
}
In der vorliegenden Lösung werden zunächst die Knoten für den Ersatztext von Grund auf neu erzeugt: die beiden Textknoten k1 und k3 sowie der dazwischenliegende Elementknoten k2 vom Typ "b" (das HTML-Tag <b>) und sein Text. Anschließend wird der bisher einzige Kindknoten von story, das <p>-Element, mithilfe von replaceChild() durch k1 ersetzt; die beiden folgenden Knoten k2 und k3 werden durch appendChild() angefügt.
Praktisch gesehen wird in diesem Beispiel der Text »Dies ist ein kurzer Text. Er besteht aus zwei Teilen.« gegen den neuen Inhalt »Hier folgt der zweite Teil des kurzen Textes.« ausgetauscht.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.