What is dynamic HTML object modeling


19.7 DHTML and the object models of browsers

Since the four versions of the two most important browsers were introduced in 1998, the catchphrase Dynamic HTML (DHTML for short) has been one of the favorite words of all web designers. This is remarkable for a term that does not describe any uniform technology: The name DHTML was created in the marketing departments of Microsoft and Netscape. It is the collaboration between style sheets for formatting HTML content and newer JavaScript capabilities.

The different object models

In particular, so-called object models were introduced for the first time in the 4-browser browsers, which make it possible to address a large part of the elements in HTML documents and to change them dynamically even after the document has been loaded. Annoyingly, in practice you have to consider three different object models that are incompatible with one another:

The W3C has standardized the Document Object Model (DOM). It consequently enables subsequent changes to any element of an HTML document by understanding the document as a hierarchically nested tree structure. The DOM is not only intended for JavaScript and HTML, but has been implemented in many different programming languages ​​for access to XML documents of all kinds. For example, Chapter 15, XML, briefly introduces the use of the DOM in Java.
DOM is interpreted by the following browsers: Internet Explorer from 5.0, Netscape from 6.0, Mozilla and Opera from 6.0.
Internet Explorer 4.0 and higher supports its own object model, the possibilities of which go almost as far as with DOM. However, access to the elements works completely differently. This model can also be used in newer versions of Explorer, although they also support DOM.
Netscape Communicator 4.x's own object model offers significantly fewer options than the other two. Basically, it is limited to the manipulation of layer objects, that is, to special
tags that can be freely positioned and that Netscape has equipped with an independent document object for changing content.
The really annoying thing about this object model, however, is that it is no longer supported by Netscape versions 6.0 or later; these browsers only use DOM. As a result, many old DHTML sites on the Web that only differentiate between the old Internet Explorer and the old Netscape object models are no longer functional in newer Netscape versions and under Mozilla.

19.7.1 W3C DOM

With the help of DOM you can access every single element of a web page. For this purpose, the object is viewed as a tree model made up of different types of nodes. Each node can have any number of child nodes. Each node has one of the node types shown in Table 19.1, which can be queried using its nodeType property:


Node typeimportance
1 Element (HTML tag)
2 Attribute (does not work like this!)
3 simple text
8 HTML comment
9 the document itself

There are two different ways of accessing a node of the HTML tag type:

The method
document.getElementById (ID string)
returns a reference to the tag to which a special ID was assigned via the id attribute. For example, you can click on a paragraph that has been defined
<p id="test">Spezieller Absatz, Marke test</p>
access using this DOM method:
document.getElementById ("test")
With the help of the method
document.getElementsByTagName (tag string)
get a reference to an array of all elements that correspond to the specified HTML tag. For example, the following expression corresponds to the third

tag on a page:

document.getElementsByTagName ("p") [2]

You cannot access text and comment nodes directly. They are always child nodes of the enclosing HTML tags. You can access the child nodes of an element as well as its parent node and its "siblings" with the help of the properties shown in Table 19.2:


propertyimportance
Node.firstChild returns the first child element of nodes
Node.childNodes [] an array of all child nodes of nodes
Node.lastChild returns the last child node of nodes
Node.parentNode returns the higher-level node
Knot.nextSibling returns the next "sibling node", that is, the subsequent child node of the same parent node
Knot.previousSibling returns the previous "sibling node"
HasChildNodes () this method returns true if the node has child elements, otherwise false

Text node properties

HTML tag nodes have the nodeName property, which contains the name of the actual HTML tag. Text and comment nodes, on the other hand, have the nodeValue property, which contains the text content. nodeValue may return something different than what you expected. For example, consider the following excerpt from the body of an HTML document:

<p id="test">Dies ist der <i>alte</i> Text.</p> <script language="JavaScript" type="text/javascript"> <!--   alert (document.getElementById ("test") .firstChild.nodeValue);   //--> </script>

The alert () statement first accesses the paragraph with the ID test via the getElementById () method. Then it reads the text content of its first child node (firstChild) via nodeValue. You might be surprised to hear that the result is not this:

This is the old text

Rather, all you get to see is:

this is the

The text "This is the" forms the first child node of the paragraph, the HTML tag ... is the second, and the rest of the "text" is the last.

Change text node

The next example uses some of these methods and properties to display the current time in the body of a paragraph. The paragraph itself is defined as follows:

<p id="uhr">Uhrzeit</p>

The head contains the following function, which should be called in the tag in the onload event handler:

function time () {var now = new Date (); var std = now.getHours (); var min = now.getMinutes (); var sec = now.getSeconds (); var time = std <10? "0": ""; time specification + = hrs + ":"; time specification + = min <10? "0": ""; time specification + = min + ":"; time specification + = sec <10? "0": ""; time specification + = sec; document.getElementById ("clock"). firstChild. nodeValue = "It is" + time + "clock"; setTimeout ("time ();", 1000); }

The date and time display was discussed earlier in this chapter. Only the line is new here

document.getElementById ("clock"). firstChild. nodeValue = "It is" + time + "clock";

The paragraph is addressed with the ID clock via getElementById (). Its first child node, firstChild, is the paragraph text, which is changed by assigning a value to its nodeValue property.



A DOM tree display

The following example recursively walks through the DOM tree of the current document and outputs information about all nodes that it finds in a separate window. Figure 19.3 shows the script at work.

Listing 19.9 first shows the source code for the entire HTML document.

Listing 10/19 Output the DOM tree of the current document

<html> <head> <title>DOM-Baumdiagramm</title> <script language="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 ("&nbsp;&nbsp;&nbsp;&nbsp;"); } 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>

Basically, the script only uses functions that have already been discussed, so it doesn't need much explanation.

For the actual recursion, the knoten.hasChildNodes () method is used to check whether there are any child nodes. If this is the case, then they are run through in a loop over all elements of the array knoten.childNodes []. The function itself is called for each child element; the value of the variable indentation increased by 1 is transferred in order to make the correct indentation.

Finally, please note that the explicit declaration of the loop counters i and j using var is absolutely necessary here, because otherwise they would be regarded as global variables and would have the wrong values ​​during recursion.

DOM application in practice

The most important area of ​​application of DOM is to make subsequent changes to the structure and content of the document. Most commonly it is used to change the positioning and other style sheet-defined properties of layers. The layers already introduced in Chapter 16, HTML, are free floating

elements that are set at a certain point using the CSS attribute position. The defined position can be changed afterwards in order to generate animations. Apart from that, you can also change any other CSS property, for example colors, font formatting, general visibility or the stacking order.

Dynamically change CSS formatting

You can use the DOM style property to access the style sheet formatting of layers (and any other HTML elements) and to change them dynamically. Style has sub-properties whose names match the original CSS attributes. For example, you can use top and left to change the position of an absolutely positioned layer or use color to modify the font color. The only peculiarity applies to those attributes whose CSS name contains a hyphen: Instead of this special character, the following letter is capitalized in the usual JavaScript identifier convention - background-color, for example, becomes backgroundColor; text-align becomes textAlign.

The values ​​for the respective style properties are strings, the content of which is specified in the same way as for style sheet specifications. For example, consider the following paragraph:

<p id="info">Der Hintergrund dieses Absatzes kann gelb werden!</p>

You can use the following JavaScript statement to color its background yellow as promised:

document.getElementById ("info"). style .backgroundColor = "# FFFF00";

It is interesting in this context that newer browsers support event handlers such as onmouseover or onmouseout for almost every element. For example, it is now widely used in large tables to highlight the row or cell in which the cursor is currently located by changing the background color. The following function can be called by such a handler to perform the color change:

function emphasize (id, color) {document.getElementById (id). style.backgroundColor = color; }

Here you can see a table row that changes its own background color when you touch the mouse using this function:

<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>

Manipulate layers

The manipulation of the properties of layer objects works in the same way. Remember that a

element only becomes a real layer if it is set to a fixed position using the CSS position attribute. Since HTML tag nodes are easiest to address via their ID, it makes sense to use an independent style specification for the CSS formatting for the layer, which is then assigned to the layer at the same time as its ID.

The example in Listing 19.10 lets a layer with an image move from the left into the visible area of ​​the window after waiting three seconds after loading; it then stops for five seconds and is then faded out. Advertising according to this scheme can be seen on more and more sites today.

Listing 11-19 An intrusive advertising popup in one layer

<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>

For a more convincing application than this example, which could annoy users, see the last subsection of this chapter, which presents cross-browser solutions.

Change and exchange document content

The structure of the DOM tree that forms an HTML document can be manipulated in any way in order to completely exchange content for others. For this purpose, node objects are equipped with a number of methods that enable corresponding manipulations. Table 19.3 shows an overview of this.


methodNode type (s)importance
createElement (tag name) document creates a new HTML tag node of the specified type
createTextNode (text) document creates a new text node with the specified content
hasAttribute (name) element (HTML tag) true if the tag has the named attribute
getAttribute (name) element returns the attribute with the given name
setAttribute
(Name, value)
element sets the attribute identified by name to value
removeAttribute
(Surname)
element removes the named attribute
appendChild (node) all appends node as the last new child
removeChild (node) all removes the specified child node
replaceChild
(newknot, oldknot)
all replaces oldNode with newNode

The example in Listing 19.11 exchanges the content of a complete paragraph that consists of several text and element nodes.

Listing 12/19 Exchanging DOM nodes

<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&ouml;nnen Sie durch Klick auf den Link &quot;Weiter&quot; lesen.</p></div> <p><a href="javascript:weiter();">Weiter</a></p> </body> </html>

Since it is known from the start that the entire content of the

element should be exchanged with the ID story, this was packed between the

and

tags. A possible alternative would be to remove all story child nodes using a loop:

while (document.getElementById ("story") .hasChildNodes ()) {document.getElementById ("story"). removeChild (document.getElementById ("story"). firstChild); }

In the present solution, the nodes for the replacement text are first generated from scratch: the two text nodes k1 and k3 as well as the element node k2 of type "b" (the HTML tag ) lying between them and its text. Then the only child node of story so far, the

element, is replaced by k1 using replaceChild (); the two following nodes k2 and k3 are added by appendChild ().

In practical terms, in this example the text “This is a short text. It consists of two parts. «Replaced by the new content» Here follows the second part of the short text «.


19.7.2 The classic Internet Explorer model

Similar to the W3C DOM, in Microsoft's own object model, which was introduced with Internet Explorer 4.0, you can in principle access every single element of a web page. Here, too, the simplest option is to access those HTML tags that have been assigned a unique ID via an id attribute. Current Internet Explorer versions since 5.0 support the DOM and the Microsoft object model at the same time.

document.all

Each element that has an ID is an element of the large document.all object array, in which the respective ID forms the index or a corresponding named sub-object. For example, you can click the table row

<td id="wichtig">wichtiger Inhalt</td>

using the following formulation:

document.all.important

The alternative is:

document.all ["important"]

With this object model, too, the most important property of such an object that you can access is the CSS formatting style.

For example, consider the following paragraph:

<p id="demo">Dies ist ein Absatz.</p>

You can use the following statement to subsequently set the text of this paragraph in bold:

document.all.demo.style.fontWeight = "bold";

The manipulation of layers also works accordingly. For example, look at this layer definition:

<div id="ebene" style="position: absolute; left: 100px; top: 100px">Test</div>

You can use the following instructions to set the level layer to a new position (200 pixels from the left edge and 200 pixels from the top):

document.all. Ebene.style.left = "200px"; document.all. Ebene.style.top = "200px";

Of course, the CSS format specification for positioning the layer does not necessarily have to be made in a style attribute within the tag, but can also be made as an independent style for the ID of the layer or in another way.

Exchange content

Another interesting property of the objects in document.all is innerHTML: by assigning it a different value, you can change any content of the document.

For example, we want to change the content of the italic text area in the following paragraph:

<p>Dies ist der <i id="kursiv">alte</i> Text.</p>

This can be done with the help of the instruction:

document.all.kursiv.innerHTML = "new";

19.7.3 The classic Netscape model

With Communicator 4.0, Netscape introduced its own DHTML object model. Compared to DOM and the Microsoft model, it has only very limited possibilities. Exactly for this reason it was completely abolished with the successor to the 4.x versions of Netscape, version 6.0: Newer Netscape versions and the open source version Mozilla only support the W3C DOM.

Limits of this model

You cannot modify any content of the HTML document using the old Netscape model. Only the CSS properties of layer objects can be changed.

The layers themselves are created in exactly the same way as in Internet Explorer and as shown in Chapter 16, HTML and XHTML: A

tag with a unique ID and defined formatting is set up.

In JavaScript, a layer is addressed via the document.layers array. The assigned ID of the respective

element is in turn used as an index or sub-object of this object:

A layer that you have over

<div id="test" style="position: absolute; left: 10px; top: 10px">Textinhalt</div>

can be referenced via JavaScript as follows:

document.layers.test

The acceptable alternative is

document.layers ["test"]

CSS manipulation

The individual CSS properties of the layer are immediate properties of the object; there is no parent style object. The CSS attributes themselves are the same as for the other object models, except that numeric values ​​are assigned as integers instead of strings with built-in units of measure. The unit of measurement must therefore be specified in the original style sheet definition and cannot be changed afterwards.

For example, you can move the test layer further to the right like this:

document.layers.test.left = 50;

The actual peculiarity of the Netscape layer is revealed when you want to access its content: In this object model, layer elements have their own inner document object, their content is not directly part of the HTML document. This is particularly evident when accessing images or forms.

Suppose you want to swap the following image in a layer for another:

<div id="rahmen" style="position: absolute; left: 200px; top: 100px"><img src="test.gif" name="bild" width="300" height="200" /></div>

This image is not addressed as a document.image, as might be expected, but as follows:

document.frame.document.image

Annoyingly, the image is accessed in all other browsers - for example in Internet Explorer, but also in new Netscape versions - via document.bild.

On the other hand, the inner document object of a layer offers the possibility of subsequently exchanging the entire content of the layer. In the frame layer from the example above, the text "No more picture" should appear instead of the picture. This is achieved with the help of the technology that was presented above for customized browser windows:

document.rahmen.document.open (); document.rahmen.document.write ("No more picture"); document.rahmen.close ();

Incidentally, the content that you write into the document object of the layer using write () can contain any HTML code.


19.7.4 Cross-Browser Solutions

Of course, it would be ideal if there were only DOM-enabled browsers overnight. Unfortunately, however, the most diverse variants are used in parallel. In this respect, the whole of DHTML is really only useful if you write scripts that automatically determine the current browser and can deal with all three object models.

At this point, the practical use of browser switches is required, which has already been discussed above in the section on browser and window properties. The good old hint that pages are only optimized for certain browsers just seems rather unprofessional. They should at least support Internet Explorer and Netscape version 4.0 or higher.

The range of DHTML features that you can apply using cross-browser solutions is not that great. The old Netscape model in particular offers relatively few options due to its restriction to the manipulation of layers. In this subsection, the two most important processes are presented in such a way that they run in all versions of the two browsers from version 4: the layer animation and the exchange of text within a layer.

Generalized layer animation

As a practical example of the animation of layers, you can see a working analog clock here: Three colored points that serve as pointers are moved on circular paths around the dial according to the system time (see Figure 19.4).



First, in Listing 19.12, you can see the complete code.

Listing 19.13 A browser-independent DHTML analog clock

<html> <head> <title>DHTML-Analoguhr</title> <script language="JavaScript" type="text/javascript"> <!--   // DHTML-Version ermitteln var dom = document.getElementById ? true : false; var ie = document.all ? true : false; var n4 = document.layers ? true : false;   // Globale Variablen var std_radius = 80; var min_radius = 100; var sek_radius = 90; var x = 110; var y = 110; function zeit () { var jetzt = new Date(); var std = jetzt.getHours(); var min = jetzt.getMinutes(); var sek = jetzt.getSeconds(); if (std > 11) std -= 12; // 12-Stunden-Anzeige! // Winkel ermitteln var std_winkel = std * 30 - 90 + min * 0.1 - 90; var min_winkel = min * 6 - 90 + sek * 0.1 - 90; var sek_winkel = sek * 6 - 90; // Zeigerpositionen berechnen var std_y = Math.round (y + std_radius * Math.sin (std_winkel * Math.PI / 180)) - 10; var std_x = Math.round (x + std_radius * Math.cos (std_winkel * Math.PI / 180)) - 10; var min_y = Math.round (y + min_radius * Math.sin (min_winkel * Math.PI / 180)) - 8; var min_x = Math.round (x + min_radius * Math.cos (min_winkel * Math.PI / 180)) - 8; var sek_y = Math.round (y + sek_radius * Math.sin (sek_winkel * Math.PI / 180)) - 5; var sek_x = Math.round (x + sek_radius * Math.cos (sek_winkel * Math.PI / 180)) - 5; // Zeiger setzen if (dom) { document.getElementById ("std").style.top = std_y + "px"; document.getElementById ("std").style.left = std_x + "px"; document.getElementById ("min").style.top = min_y + "px"; document.getElementById ("min").style.left = min_x + "px"; document.getElementById ("sek").style.top = sek_y + "px"; document.getElementById ("sek").style.left = sek_x + "px"; } else if (ie) { document.all.std.style.top = std_y + "px"; document.all.std.style.left = std_x + "px"; document.all.min.style.top = min_y + "px"; document.all.min.style.left = min_x + "px"; document.all.sek.style.top = sek_y + "px"; document.all.sek.style.left = sek_x + "px"; } else if (n4) { document.layers.std.top = std_y; document.layers.std.left = std_x; document.layers.min.top = min_y; document.layers.min.left = min_x; document.layers.sek.top = sek_y; document.layers.sek.left = sek_x; } setTimeout ("zeit();", 1000); }   //--> </script> </head> <body onload="zeit();"> <div id="zifferblatt" style="position: absolute; top: 0px; left: 0px"> <img src="zifferblatt.gif" width="220" height="220" /></div> <div id="std" style="position: absolute; top: 90px; left: 10px"> <img src="std_zeiger.gif" width="20" height="20" /></div> <div id="min" style="position: absolute; top: 92px; left: 20px"> <img src="min_zeiger.gif" width="15" height="15" /></div> <div id="sek" style="position: absolute; top: 95px; left: 30px"> <img src="sek_zeiger.gif" width="10" height="10" /></div> </body> </html>

DHTML browser switch

First of all, it is noticeable that a completely different type of browser switch is used here than the one discussed above: The variables dom, ie and n4 are tentatively assigned the DHTML objects from DOM, the classic Internet Explorer model or the old Netscape model as Assigned values. The result of this value assignment is an object reference if the access type works or null if it fails. In this way, the DHTML models below can easily be queried using formulations such as if (dom) or if (ie).

The conventional browser switch has a major disadvantage for DHTML purposes: Since newer Internet Explorer versions also report 4.0 as the major version number, all variants of this browser would be treated as Internet Explorer 4.0. Newer versions could then not benefit from the DOM advantages.

calculation
the pointer positions

Probably the most interesting thing about this script is the mathematical process by which the positions of the hands on the dial are calculated.

The first step is to calculate the correct angle from the hours, minutes or seconds. To do this, the 12 possible hours or 60 possible minutes and seconds are simply distributed over 360 ° by multiplication: For reasons of clarity, for example, you can also write sec / 60 * 360 instead of the already simplified information sec * 6.

The proportional minute value is added for the hour hand and the corresponding second value for the minute hand so that these two hands move continuously. Last but not least, 90 ° have to be subtracted because the starting point 12 o'clock is not on the left, but on the top.

In the second step, the angle must now be converted into x and y values ​​on a circle with a certain radius. If the sine values ​​of the angles are viewed as x-coordinates and the cosine values ​​as y-coordinates, the correct basic values ​​related to 1 can be determined, which only have to be multiplied by the radius and added to the respective axis value of the center point . For the hour hand (radius 80 pixels), this results in the values ​​sin (x) * 80 + 110 and cos (y) * 80 + 110.

The only minor problem is that the trigonometric functions in JavaScript use radians; for the conversion from the angular dimension, 360 ° = 2 applies.

Finally, the radius of the respective pointer image is subtracted in order to really position the pointer correctly.

After the calculation has been completed, the three pointer layers can be set to the calculated x and y values. For this purpose it is queried which object model determined at the beginning of the function is supported so that every browser is addressed with the syntax that suits it.

Exchange of layer contents

As an example for the exchange of text content, the following listing shows how long the visitor has been viewing the page in the duration layer.

You can see the code in Listing 19.13.

Listing 19.14 Cross-browser exchange of layer texts

<html> <head> <title>Ihre Verweildauer</title> <script language="JavaScript" type="text/javascript"> <!--   // DHTML-Version ermitteln var dom = document.getElementById ? true : false; var ie = document.all ? true : false; var n4 = document.layers ? true : false;   // Bisherige Verweildauer var zeit = 0; function zeigeDauer() { var infostr = "Sie sind bereits " + zeit + " Sekunden hier."; if (dom) document.getElementById ("dauer"). firstChild.nodeValue = infostr; else if (ie) document.all.dauer.innerHTML = infostr; else if (n4) { with (document.layers.dauer.document) { open(); write (infostr); close(); } } zeit++; setTimeout ("zeigeDauer();", 1000); } //--> </script> </head> <body onload="zeigeDauer();"> <div id="dauer" style="position: absolute; top: 10px; left: 10px">So lange sind Sie schon hier!</div> </body> </html>

The first thing to do is to determine which DHTML version is supported. Then the string to be displayed is tinkered, which is then displayed in the appropriate way depending on the browser.

Please note that you must not write HTML into such an output string if the script is also to work in DOM browsers: While the traditional Netscape method and the old Internet Explorer property innerHTML have no problem with this, HTML tags are subordinate to it DOM represent separate nodes that cannot simply be entered as a string in the nodeValue of a text node.