ISBN 1-57231-686-1
Chapter 9 and the Table of Contents of Inside Dynamic HTML reprinted with permission from Microsoft Press.
http://www.microsoft.com/mspress/
Acknowledgments
Introduction
Part I: HTML and Scripting
Chapter One
Overview of HTML and CSS
New HTML Features
Cascading Style Sheets
Examining an HTML DTD
Chapter Two
Fundamentals of HTML Scripting
Dynamic HTML Object Hierarchy
Authoring Scripts
Choosing a Scripting Language: JavaScript vs. VBScript
Advanced JavaScript Techniques
Scripting and Web Security
Chapter Three
Dynamic HTML Event Model
General Event Model
Event Binding
The event Object
Programming Standard User Events
Event Examples
Chapter Four
The Browser Window
The window Object
The Window Environment
Window Events
Timer Events
The clientInformation or navigator Property
Chapter Five
Window and Frame Management
Manipulating the Window
Creating New Windows
Manipulating Framesets
Special Event Considerations
Part II: document structure
Chapter Six
The HTML Document
Referencing the document Object
Changing the Document's Colors
Accessing Meta Information About the Document
Modifying the HTML Stream
Chapter Seven
Document Element Collections
Using the Collections
The HTML Structure and Collections
Chapter Eight
Scripts and Elements
Identifying Elements
Accessing an Element's Attributes
Parsing Information
Creating New Elements
Customizing Elements
Chapter Nine
Scripting Individual Elements
Programming the Body and Frameset Elements
Programming the Anchor Element
Programming the Link Element
Programming the IMG and Map Elements
Programming the Marquee Element
Programming the Object Element
Programming the Table Element
Chapter Ten
Forms and Intrinsic Controls
HTML Forms
Programming Text Input Elements
Programming List Elements
Programming Lists Using Radio Buttons and Check Boxes
Programming Command Button Elements
Programming Label and Fieldset Elements
Part III: document style and animation
Chapter Eleven
Dynamic Styles
Dynamic Styles and CSS
Style Sheet Properties
Inline Styles
Changing the class Attribute
Global Style Sheets
Adaptive Layout Techniques
Data Display Techniques
Text Animation Techniques
Chapter Twelve
Dynamic Positioning
CSS Positioning
Scripting CSS Positioning
The Rendering Context
Part IV: document contents and data binding
Chapter Thirteen
Dynamic Contents
Contents Manipulation
Dynamic Contents Properties
Dynamic Contents and document.write
Chapter Fourteen
User Selection and Editing Operations
Introducing the TextRange Object
Programming the TextRange Object
Accessing the User's Selection
Executing Commands
Chapter Fifteen
Data Binding with HTML
What Is Data Binding?
Data-Binding Architecture
Data Consumers: HTML Elements
Building Basic Pages Using Data Binding
Writing Scripts Using Data Binding
Advanced Features
Index
Chapter 8, "Scripts and Elements," introduced scripting elements in Dynamic HTML. While every element in an HTML document is accessible to scripts, this chapter focuses on techniques for programming a few of the elements that appear most commonly in scripts. Chapter 10, "Forms and Intrinsic Controls," describes techniques for programming elements in forms.
The following topics are covered in this chapter:
| • | Programming the Body and Frameset elements HTML defines two types of documents: documents with Body elements for displaying contents, and documents with Frameset elements for dividing the screen into frames for loading other documents. This section introduces techniques for manipulating these elements. |
| • | Programming the Anchor element Anchor elements serve a dual purpose in HTML: as links that navigate to new pages, and as bookmarks that act as destinations. This section discusses how to manipulate both types of Anchor elements and how to add custom behavior to them. |
| • | Programming the Link element The Link element is used to define relationships between documents. Microsoft Internet Explorer 4.0 supports the Link element for specifying linked style sheets. This section shows you how to define and take advantage of custom relationships between documents. |
| • | Programming the Image and Map elements Dynamic HTML exposes a rich object model for manipulating images and image maps. New images can be loaded in the background so that they can be displayed instantly, and image maps can be dynamically modified and scaled. |
| • | Programming the Marquee element The Marquee element is currently an Internet Explorer-specific feature used to automatically scroll a block of text. The Marquee element can be customized and manipulated using the Dynamic HTML object model. |
| • | Programming the Object element The Object element is used to embed custom contents, including ActiveX controls and Java applets, in the HTML document. The custom contents can expose a customized object model alongside that of the Object element. This section shows you how to access this object model. |
| • | Programming the Table element Tables are used for two purposes: displaying tabular data in a gridlike format, and creating a sophisticated layout. This section discusses the relationship between the underlying HTML for the table and the object model representation. |
An HTML document can contain either of two types of contents: body contents or a frameset definition. The first Body or Frameset element appearing in the document defines the document's type. A similar object model is exposed for the document in both cases.
The body Property
The document object exposes a body property that represents the root of the document's contents. The name of this property is ambiguous because the body property can represent either a Frameset or a Body element, depending on the document type. As explained in Chapter 7, "Document Element Collections," every document has a Body or Frameset element, regardless of whether it is explicitly declared. If a document's frameset nests other Frameset elements, the body property represents the outermost Frameset element in the document.
The Body or Frameset element is also contained in the document's all collection. Thus, the body property can be accessed directly from the document as follows:
// Returns BODY or FRAMESET depending on the type of document. document.body.tagName;
Or it can be accessed through the all collection:
// For documents with a Body element
document.all.tags("Body").item(0).tagName; // Returns "BODY"
/* Displays "true"; demonstrates that the two elements are the
same */
alert(document.all.tags("Body").item(0) == document.body);
// For documents with a Frameset element
document.all.tags("Frameset").item(0).tagName; // Returns "FRAMESET"
/* Displays "true"; demonstrates that the two elements are the
same */
alert(document.all.tags("Frameset").item(0) == document.body);
In the preceding code, the tags method returns a collection consisting of the Body or the Frameset elements. If the document has a Body element, the HTML DTD (document type definition) limits it to a single Body element, and the parser ignores any extra ones. If the document has a Frameset element, it can have multiple Frameset elements; the tags method returns all of them, beginning with the outermost one. In either case, the first element in the collection returned by the tags method is the element contained in the body property. The code uses item to access this element.
Availability of the body Property
The object model is constructed and exposed simultaneously during the parsing of the document. Before the parser encounters the body or frameset of the document, the body property is not available, and therefore the body property returns null. The following code illustrates the availability of the body property:
<HTML> <SCRIPT LANGUAGE="JavaScript"> alert(document.body == null); // true--precedes <BODY> tag </SCRIPT> <BODY> <SCRIPT LANGUAGE="JavaScript"> alert(document.body == null); // false--follows <BODY> tag </SCRIPT> </BODY> </HTML>
For documents with body contents, the <BODY> tag does not have to appear explicitly in the document to be accessible. Instead, the Body element is implicitly created once the document contains an element--or simply some text--that must be a part of the body. The elements that make up body contents are defined by the HTML DTD. Chapter 1, "Overview of HTML and CSS," explains how to read a DTD, and more information about how the document is parsed is provided in Chapter 7, "Document Element Collections."
Distinguishing Between Body and Frameset Contents
You can use the tagName property to determine whether a document contains a body or a frameset. The following code displays an alert box reporting its document type--in this case, a frameset:
<HTML> <HEAD> <TITLE>Frameset Exposed as the Body</TITLE> </HEAD> <FRAMESET ROWS="100%" ONLOAD="alert(document.body.tagName);"> <FRAME SRC="foo.htm"> </FRAMESET> </HTML>
Checking the length of the frames collection on the window is not an accurate way to determine whether a document is a frameset. A document with a Body element may contain IFrame elements, which would be included in the frames collection.
Client Window and Document Size
The width and height of the client window are exposed as properties of the Body and Frame elements. The physical size of the document is the size of the client area-- that is, the amount of space the document occupies on the screen. The logical size of the document is the size of the contents. For document contents that are larger than the window, scrollbars are usually displayed. Figure 9-1 illustrates the properties that represent the physical and logical size of the document, and the subsequent sections describe them. Other elements in the document can expose the same properties for determining their size. The special relationship these properties share with other elements in the document is discussed in Chapter 12, "Dynamic Positioning."
Physical Size
The physical width and height of the document (frameset or body type) are exposed through the offsetWidth and offsetHeight properties of the Frameset or Body element. The physical width and height measure the area of the currently visible window including the scrollbars. The clientWidth and clientHeight properties are exposed to determine the size of the client area--the physical size as defined by the offsetWidth and offsetHeight properties less the size of the scrollbars and surrounding borders. These properties are read-only and cannot be used to change the size of the window.
In Figure 9-1, no horizontal scrollbar is displayed, so the offsetHeight and clientHeight properties would be the same if the border was set to 0. However, a vertical scrollbar is displayed, so the offsetWidth and clientWidth properties represent distinct values.
Logical Size
The Body element exposes four properties for determining the logical size of the document and the position of the user's view into the document: scrollWidth, scrollHeight, scrollTop, and scrollLeft. The logical size of the document represents the total height and width of the document, not the size of the browser window that provides a view into the document. These properties are not available or necessary on frameset documents because the logical size of the frameset is equivalent to its physical size.
The scrollWidth and scrollHeight properties represent the logical size of the document in pixels. These properties are read-only and are calculated by the browser based on the document contents. You can change the scrollWidth and scrollHeight properties by dynamically adding or removing elements or by resizing the window. Resizing the window usually affects both properties because the contents rewrap to the new width.
The scrollTop and scrollLeft properties represent the scroll offsets of the logical document. They represent the point in the document that is displayed in the upper-left corner of the window. When the horizontal and vertical scrollbars are scrolled all the way to the left and top edges of the document, scrollLeft and scrollTop both equal 0. These properties are read/write and can be modified to immediately scroll the document to a particular pixel position. If you need to set scrollLeft and scrollTop at the same time, the scroll method on the window is a more convenient mechanism because it takes both new coordinates, horizontal and vertical, as arguments.
As a group, these properties provide information for determining the visible portion of the screen. The currently viewable area of the document can be easily calculated using the size properties, as shown here:
upperLeftX = document.body.scrollLeft; upperLeftY = document.body.scrollTop; lowerRightX = upperLeftX + document.body.clientWidth; lowerRightY = upperLeftY + document.body.clientHeight;
The scrolling-related properties are also exposed on any other scrolling element. For example, you can give a Div element scrollbars using the CSS (Cascading Style Sheets) overflow3 property, and the TextArea element displays scrollbars by default. When these elements have scrollbars, they expose the scrolling-related properties for determining the scrolled regions of their contents. The TextArea element is discussed in detail in Chapter 10, "Forms and Intrinsic Controls," and the CSS overflow property is discussed in Chapter 12, "Dynamic Positioning."
Window Events
The Body and Frameset elements expose attributes corresponding to all window-level events. For example, the following code in a document with a Body element specifies an ONLOAD event handler for the window:
<BODY ONLOAD="doThis();">
The code for a frameset document is similar:
<FRAMESET ONLOAD="doThis();" ROWS="*">
Even when you use the <BODY> or <FRAMESET> tag to specify the handler for a window event, the event is scoped to the window object, not to the body object. This distinction is important when you use the this pointer in the event handler. In a body-level event handler, this points to the body object; in a window event handler, this points to the window object, even if you specify the handler in the <BODY> tag. The following code illustrates how this pointers work for a window event (onload) and a body event (onclick):
<BODY ONLOAD="alert(this == document.body); // false" ONCLICK="alert(this == document.body); // true"> </BODY>
Furthermore, for window events, the srcElement property of the event object contains null.
While a document can have multiple framesets, it can have only one handler for each window event. If several Frameset elements in the document define handlers for an event, only the last handler's code is executed. In the following example, only the second onload event handler executes, displaying the alert b. The event does not fire until the entire document is loaded.
<HTML> <HEAD> <TITLE>Frameset onload Event</TITLE> </HEAD> <FRAMESET ONLOAD="alert(`a');" ROWS="100, *"> <FRAMESET ONLOAD="alert(`b');" COLS="*.*"> <FRAME SRC="a.htm"> <FRAME SRC="b.htm"> </FRAMESET> <FRAME SRC="c.htm"> </FRAMESET> </HTML>
Because you can define only one handler per window event, you cannot specify a handler on a Frame or nested Frameset element that works only for that particular element. To protect against this behavior possibly changing in the future, window event handlers should be specified only on the first Frameset element.
The onresize Event
The onresize event is fired whenever the size of the physical window changes, not the size of the contents within the body or frameset document. Therefore, this event is actually a window event when defined on the Body element. The onresize event is also exposed on elements within the document that have a defined size. In those cases, the event fires only when the physical size of the element changes.
When a document is first loaded into a new window, the onresize event does not fire. Therefore, if code is being used to lay out the document based on the initial window size, the code should be called from the onload event.
Programming Body Contents
Documents that contain a Body element have a few additional features not available to frameset documents, including access to the HTML and textual contents contained within the body and an onscroll event that fires when the window is scrolled.
You can write scripts to manipulate the text in the Body element or any element in the body. The techniques are discussed in Chapter 13, "Dynamic Contents."
The onscroll Event
The window object exposes an onscroll event that fires whenever the window is scrolled either explicitly by the user or through code. This event occurs only in documents with Body elements and not in frameset documents because they do not display scrollbars.
Programming Frameset Contents
Because the frameset document is another type of HTML document, it supports the document object model. The frameset document exposes an all collection that provides direct access to all the elements in the document. Through the all collection, the individual attributes of each Frameset and Frame element can be accessed and in many cases dynamically modified.
While the number of frames in the frameset is static and cannot be modified without creating a new document, a number of the attributes of the Frameset element can be changed. For example, the ROWS and COLS attributes are read/write attributes, which allows you to change the layout of the frameset dynamically. This flexibility can be used to add custom behavior to a traditional frameset.
The following code creates a custom layout that allows the user to select from a set of pages. This example turns off the resizing capability of each frame and instead automatically expands the frame the user clicks on. This layout model requires a small amount of code behind the frameset and each document.
<HTML>
<HEAD>
<TITLE>Sliding Frames</TITLE>
<SCRIPT LANGUAGE="JavaScript">
var defSize = 25;
function display(f) {
var newRows = "";
// Get all the Frame elements.
var elFrame = document.all.tags("FRAME");
for (var intFrames = 0; intFrames < frames.length;
intFrames++) {
var curF = frames[intFrames].document;
if (curF.body == f.document.body) {
// Give selected frame all the space.
newRows += "*, ";
/* Make the header much bigger. */
curF.all.header.style.fontSize = "200%";
/* Turn on scrollbars for the active frame
by accessing the Frame element
in the frameset document. */
elFrame[intFrames].scrolling = "yes";
}
else {
// Set to default size.
newRows += defSize.toString() + ", ";
// Reset header font size.
curF.all.header.style.fontSize = "";
// Turn off scrolling.
elFrame[intFrames].scrolling = "no";
}
}
document.body.rows = newRows;
}
</SCRIPT>
</HEAD>
<FRAMESET ROWS="*, 25, 25">
<FRAME SRC="home.htm" NORESIZE>
<FRAME SRC="news.htm" NORESIZE SCROLLING="No">
<FRAME SRC="info.htm" NORESIZE SCROLLING="No">
</FRAMESET>
</HTML>
Figure 9-2 demonstrates this code in action. When the user clicks on the News or Information heading, the other frames automatically shrink and the selected frame expands to take up the remaining view.
In each document in the frameset, the onfocus event handler must call the display routine. The parent property on the document must be referenced to call the function:
<!-- The onfocus event must be defined for each document in the frameset. --> <BODY ONFOCUS="parent.display(this);">
Also in each document in the frameset, the ID of the first paragraph must have the value header. The text in this paragraph will be enlarged when the document has the focus.
This example demonstrates modifying the attributes of individual frames. The Frame element in the all collection of the document is different from the contents of the window's frames collection. The frames collection on the window returns the window instance created based on the document's source. The Frame element in the all collection represents the frame as defined by the HTML source and is used to create the window. Modifying the Frame element can modify the window and its contents--for example, scrollbars can be manually turned on and off. Scrollbars have been turned off in our example so that they do not clutter the collapsed heading view of the document.
The HTML Anchor element serves a dual purpose: to specify links for navigating to URLs and to specify bookmarks within the document. An Anchor element acts as a link if its HREF attribute is defined, and it acts as a bookmark if its NAME attribute is defined:
<A HREF="http://www.insideDHTML.com#Chapter2"><!-- Link --></A> <A NAME="Chapter2"><!-- Bookmark --></A>
The document's all collection references all of the Anchor elements. The document has two additional collections that separately reference the links and the bookmarks. Links are exposed through the links collection, and bookmarks are exposed through the anchors collection. A single Anchor element can appear in both collections if both a NAME and an HREF attribute are specified.
Both the href and the name properties can be changed through code, so the anchor object can dynamically switch collections. For example, if an anchor with an empty href is assigned a string, it is automatically added to the links collection and is also automatically rendered as a link on the screen. Regardless of the type of anchor and the collections it is in, the anchor object exposes the same set of properties, methods, and events.
Note: From here on, anchors specified as <A NAME=> are referred to as bookmarks and anchors specified as <A HREF=> are referred to as links to disambiguate the two types of anchors. These links are different from the Link element discussed in the section "Programming the Link Element" later in this chapter. The <LINK> tag defines the Link element.
The href Property
The anchor object has a number of properties that contain portions of the URL exposed by the href property. The protocol, hostname, port, pathname, search, and hash properties reference the individual parts of the URL, and the host property contains both host name and port information. These properties, which also belong to the location object, are described in Chapter 4, "The Browser Window."
Anchors and the Base HREF
An interesting relationship exists between relatively specified HREF values and the object model. A relative HREF is a URL that does not start explicitly with // (for example, href="goHere.htm"). All relative HREFs are prefixed with a default location. Unless otherwise specified, the default location is the location of the current document. You can use the Base element to change the default location. For relative URLs assigned to attributes, the default location is added when the document is parsed. For relative URLs assigned to properties by scripts, the default location is not added until the URL is referenced. The following code illustrates these points:
<HTML> <HEAD> <TITLE>Base HREF Demo</TITLE> <BASE HREF="http://scotti/"> </HEAD> <BODY> <A HREF="page1.htm">page 1</A> <A HREF="http://ji/page2.htm">page 2</A> <SCRIPT LANGUAGE="JavaScript"> alert(document.links[0].href); // http://scotti/page1.htm alert(document.links[1].href); // http://ji/page2.htm document.links[0].href = "newpage.htm"; alert(document.links[0].href); // newpage.htm </SCRIPT> </BODY> </HTML>
Script-Specified HREF Attribute
The HREF attribute of an Anchor element may be alternatively specified as a line of code to execute rather than as a URL. This technique is useful when a frame is to contain a short string because it reduces the number of necessary round-trips with the server. For example, when the user clicks on the following anchor, a simple document that displays the string Hello, world! is created:
<A HREF="JavaScript:'Hello, world!'">
The protocol is the language name followed by a colon, and the pathname is the rest of the string. The href property itself contains the entire string with appropriate escape sequences (such as %20 for each space).
Script-specified HREF attributes execute after the onclick event stops firing. Also, since the HREF attribute is not an event, the event object is not available at the time the script-specified HREF executes.
Note: Take care when using VBScript for script-specified HREF attributes. Netscape Navigator recognizes only the JavaScript language and will display a navigation error if VBScript is specified.
Events on the Anchor Element
The Anchor element supports a set of standard events that indicate when the user clicks on, moves the mouse over, or types in an anchor. The events that can originate with the Anchor element depend on whether the anchor is a link or a bookmark. If the Anchor element cannot act as a source for the event, it will never be defined as srcElement if the event bubbles.
| Event | Source |
onblur | Link anchors |
onclick | All anchors |
ondblclick | All anchors |
onfocus | Link anchors |
onkeydown | Link anchors |
onkeypress | Link anchors |
onkeyup | Link anchors |
onmousedown | All anchors |
onmousemove | All anchors |
onmouseout | All anchors |
onmouseover | All anchors |
onmouseup | All anchors |
However, through event bubbling, the Anchor element can receive the event from a child event (such as an image in the anchor) even if it does not explicitly support it. The preceding table lists the events for which each type of anchor can act as the source. All events can be defined as attributes on the element, through the <SCRIPT FOR= EVENT= > syntax or through properties of the Anchor element. To be a source for focus and keyboard events, an element must be able to receive the focus. Anchors that are links can receive the focus; bookmarks cannot.
The default result of clicking on a link is a jump to the anchor. This action can be overridden to customize how a page handles a link. The following code cancels the default action of a specific link:
<A HREF="foo.htm#100" ONCLICK="event.returnValue = false;">
If you want compatibility with other browsers, return the value directly:
<A HREF="foo.htm#100" ONCLICK="return false;">
More generically, anchors can be overridden at the document level by handling the document's onclick event, as shown in the following code. This technique works because the standard events, except onblur and onfocus, bubble up the document's hierarchy chain.
<SCRIPT FOR="document" EVENT="onclick()" LANGUAGE="JavaScript">
// Event object contains global information for the event handler.
if ("A" == event.srcElement.tagName) {
event.returnValue = false;
// Write custom handler code for the anchor.
}
</SCRIPT>
The event sequence defines the ondblclick event to follow the onclick event. The only way to determine whether a link received a double click is to override the default action of the single click because the event sequence is fixed. There is no way to write an event handler for a link that performs a default action for a click event and a different action for the double click because the link already navigates to the targeted link before the ondblclick event fires. Therefore, the usefulness of an ondblclick event on a link is fairly limited, and most interactions with anchors are accomplished using the onclick event.
Customizing Links to Target Multiple Frames
One technique for adding custom behavior to an anchor is to define a few new attributes on the Anchor element. This technique for simulating subclassing was introduced in Chapter 8, "Scripts and Elements." This section demonstrates how to augment the traditional behavior of anchors. The simple example presented here implements the basics for a much-requested feature of HTML and framesets--the ability to target multiple frames with a single anchor. This example demonstrates how authors can add their own functionality to a page, without having to wait for the browser to add the support.
The following code adds two user-defined attributes to the Anchor element: mhref and mtarget. Both attributes take a semicolon-delimited list of values--for mhref, a list of URLs, and for mtarget, a list of destinations for these URLs. When the user clicks on an anchor, the code first checks whether the anchor has these special attributes and, if it does, the code overrides the default behavior of following a single link with the custom linking code.
<HTML>
<HEAD>
<TITLE>Targeting Multiple Frames</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function checkElementTree(el, strTag) {
/* This simple function walks up the tree from the element
el and looks for any element with the tag strTag.
The first matching element found is returned. */
while ("HTML" != el.tagName) {
if (strTag == el.tagName)
return el;
el = el.parentElement;
}
return null;
}
function multiJump() {
// Find the anchor.
var el = checkElementTree(event.srcElement, "A");
if (null != el) { // Found an anchor.
// Check whether it is a multitarget anchor.
if ((null != el.getAttribute("mhref")) &&
(null != el.getAttribute("mtarget"))) {
event.returnValue = false;
var mhref = new Array();
var mtarget = new Array();
// Parse attributes into arrays.
mhref = el.getAttribute("mhref").split("; ");
mtarget =
el.getAttribute("mtarget").split("; ");
/* Be sure there are an equal number
of targets and URLs. */
if (mtarget.length == mhref.length)
for (var intLoop = 0; intLoop < mtarget.length;
intLoop++)
if (null != parent[mtarget[intLoop]])
parent[mtarget[intLoop]].location.href =
mhref[intLoop];
}
}
}
</SCRIPT>
</HEAD>
<BODY ONCLICK="multiJump();">
<A HREF="#"
mhref="http://www.microsoft.com; http://www.netscape.com"
mtarget="left; right">
Browser Web sites
</A>
</BODY>
</HTML>
This code works only for frames that are siblings to the frame containing it. To make this code work for frames that exist anywhere in the frameset hierarchy, you must write code that simulates the searching algorithm used by the browser to search the hierarchy of windows.
Subclassing elements with user-defined attributes is one of the most powerful ways to take advantage of Dynamic HTML. It lets you easily customize elements, without having to hard-code the customizations into HTML or the scripting language. Custom attributes can be defined for identifying new behavior, and the code can look for these identifiers and process the elements accordingly.
Pseudo-Classes for Anchors
Style sheets provide a technique for defining styles for the three states of a link: visited, not visited, and active. These states can each have a different style, which you set using pseudo-classes in CSS. Pseudo-classes provide a technique for improving user interactivity without requiring any code. See Chapter 1, "Overview of HTML and CSS," or the CSS specification at the W3C Web site for more information about pseudo-classes and the CSS language.
Beyond using the pseudo-class, no property is currently exposed to the scripting language for directly determining whether a link has been visited. Therefore, there is no simple way to conditionally script links based on whether they have been visited.
Removing Anchors
Simply assigning an empty string to either the href or the name property does not remove an Anchor element from the document. However, this technique will remove the element from the links or anchors collection, respectively. (The element will always remain in the all collection.)
An Anchor element and its contents can be completely removed from the document by using the outerHTML or outerText properties. To remove the influence of the anchor but leave the contents, the TextRange object can be used. The following code demonstrates how to manipulate TextRange. Don't worry if you do not understand this code. The TextRange object and its methods are discussed in detail in Chapter 14, "User Selection and Editing Operations."
<SCRIPT LANGUAGE="JavaScript">
function removeAnchor(aElement) {
// The anchor to remove is passed as an argument.
// Create a TextRange object.
var tr = aElement.parentTextEdit.createTextRange();
// Locate the Anchor element in the textRange.
tr.moveToElementText(aElement);
// Execute a command to remove the Anchor element.
tr.execCommand("Unlink", false);
}
</SCRIPT>
Programming the Link Element
The previous section showed you how to program an Anchor element that is either a bookmark or a link. HTML also provides a Link element that can be used to define relationships between different types of documents. This section focuses on a technique for defining relationships between documents using the Link element and the REL and HREF attributes, which can be accessed from scripts.
At the time of this writing, Internet Explorer uses link relationships for style sheets. However, by writing some simple scripts, you can use the REL attribute to define other relationships. Defining relationships not only can make your Web site more manageable, but it also can make the Web site accessible to tools that analyze Web sites.
The following example demonstrates how to create a navigation bar that reads each document's Link element to ascertain the next and previous documents. A navigation bar is useful when a sequence of documents is being presented. The navigation bar and contents panes are defined through a simple frameset. Whenever a new document is loaded, the document calls a function on the frameset to update the navigation buttons based on the new document's links.
Figure 9-3 shows the navigation bar in action. The availability of the buttons in the top pane and their destination when clicked are defined by Link relationships.
The links.htm Document
The links.htm document, shown in the following code, defines the frameset and contains the core code for managing the relationship between the links on the page and the navigation bar. Each document displayed in the contents frame must call the setupLinks function after it loads in order to update the navigation bar of the navigation pane. When the page unloads, the clearLinks method must be called in order to disable all the relationship buttons, thereby ensuring that the links are appropriate if the user navigates to a page that does not define any relationships.
<HTML>
<HEAD>
<TITLE>Link Relationships</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function setButton(b, dis, title, href) {
b.disabled = dis;
b.title = title;
b.href = href;
}
function clearLinks() {
var navDoc = window.navigation.document.all;
// Initialize buttons by disabling them
// and removing their titles.
with (navDoc) {
setButton(previous, true, "", "");
setButton(next, true, "", "");
}
}
function setupLinks(doc) {
// The calling document needs to be passed in.
// Get all the Link elements.
var links = doc.all.tags("LINK");
var navDoc = navigation.document.all;
clearLinks();
for (var intLink = 0; intLink < links.length; intLink++) {
var el = links[intLink];
if ("previous" == el.rel) {
/* If a previous relationship is defined, update
the buttons. */
setButton(navDoc.previous, false, el.title,
el.href);
}
if ("next" == el.rel) {
/* If a next relationship is defined, update
the buttons. */
setButton(navDoc.next, false, el.title, el.href);
}
}
}
</SCRIPT>
</HEAD>
<FRAMESET ROWS="28, *" BORDER=0>
<FRAME SRC="navigate.htm" NAME="navigation" SCROLLING=NO>
<FRAME SRC="contents.htm" NAME="contents">
</FRAMESET>
</HTML>
The navigate.htm Document
This code creates the navigation bar:
<HTML>
<HEAD>
<TITLE>Navigation Bar</TITLE>
<STYLE TYPE="text/css">
body {margin-top:2pt; margin-left:2pt; background:gray}
input {font-weight:bold}
</STYLE>
</HEAD>
<BODY>
<INPUT TYPE=BUTTON VALUE="TOC" TITLE="Table of Contents"
ONCLICK="top.contents.location = `contents.htm';">
<INPUT TYPE=BUTTON ID="previous" VALUE=" < "
ONCLICK="parent.contents.location = this.href;">
<INPUT TYPE=BUTTON ID="next" VALUE=" > "
ONCLICK="parent.contents.location = this.href;">
</BODY>
</HTML>
Note: The buttons in this example are drawn with extra spaces between them because carriage returns separate their tags in the code. To close the gap between the buttons, remove the carriage returns and all spaces between the Input elements.
The contents.htm Document
The following code is a sample contents file that defines a link relationship to the next document in the sequence. When this document loads, it must call the setupLinks function to update the available links, and when it unloads it must call clearLinks.
<HTML> <HEAD> <TITLE>Contents</TITLE> <!-- Only a next relationship is defined. The Previous button will be disabled for this document. --> <LINK REL="next" HREF="chapter1.htm" TITLE="Chapter 1"> </HEAD> <BODY ONLOAD="parent.setupLinks(window.document);" ONUNLOAD="parent.clearLinks();"> <H1>Table of Contents</H1> </BODY> </HTML>
This example demonstrates two simple relationships, but it can be easily extended with more relationships to provide an enhanced toolbar in the navigation pane.
Images and image maps are fully programmable in Internet Explorer 4.0. You can now change the SRC attribute and size of an image and modify, add, and remove Area elements from an image map. The object model also allows new images to be asynchronously downloaded in the background while the user interacts with the page. This section presents techniques for downloading images and for manipulating the Image element and associated image maps.
Image Animation
One common technique for animating images is to change the image as the mouse enters and exits the element. In Internet Explorer 4.0, this task is trivial--you use the onmouseover and onmouseout events on the Image element itself:
<IMG SRC="start.gif" ONMOUSEOVER="this.src = `over.gif';" ONMOUSEOUT="this.src = `start.gif';">
Netscape Navigator will ignore this code because it does not currently support onmouseover and onmouseout events on the Image element. Netscape Navigator does support these events on the Anchor element, however. Therefore, with a little forethought it is possible to re-create the preceding scenario in a more compatible way. By wrapping the Image element in an Anchor element, both Netscape Navigator 3.0 or later and Internet Explorer 4.0 will properly change the image:
<A HREF="" ONMOUSEOVER="document.myImage.src = `over.gif';" ONMOUSEOUT="document.myImage.src = `start.gif';"> <IMG BORDER=0 NAME="myImage" SRC="start.gif"> </A>
The BORDER=0 attribute must be added so that the default anchor border is not drawn around the image. And while this technique does provide similar support in both Netscape Navigator and Internet Explorer, there is still one key difference. Because no size is supplied to the image, in Internet Explorer the container of the image is automatically resized to match the image and the surrounding contents are reflowed. In Netscape Navigator, the size of the image is fixed when the first image is loaded, so the next image is scaled to fit. To work around this discrepancy, either ensure that the images are the same size or provide width and height attributes on the Image element.
While the preceding code works, a noticeable delay might occur when the second image is initially downloaded. Dynamic HTML supports the ability to preload an image behind the page so that it is immediately available for use.
Image Sequencing
Timer events can be used instead of user-generated events to change an image. Dynamic HTML makes it simple to create an image sequencer that rotates images after a specified amount of time. Images can be preloaded using a special image constructor, and the Image element's SRC attribute can be dynamically changed.
The following code shows the application of this technique, a client-side billboard that cycles through images after a specified amount of time. This scenario uses unrecognized elements to define the list of advertisements. The advantages of this model are that new ads can be added and outdated ads can be removed without having to modify any code. Another technique used in this example is to preload the images before assigning the SRC attribute to ensure a smooth transition from image to image. An error recovery mechanism is included to skip an image if it fails to download.
<HTML>
<HEAD>
<TITLE>Ad Sequencing</TITLE>
<!-- More ads can be added simply by extending this list. -->
<ADLIST src="ad1.gif" duration=3000>
<ADLIST src="ad2.gif" duration=5000>
<ADLIST src="ad3.gif">
<ADLIST src="ad4.gif" duration=1000>
<SCRIPT LANGUAGE="JavaScript">
var adSet = document.all.tags("ADLIST");
adSet.current = 0;
var nextImage = document.createElement("IMG");
function preLoad() {
// Get next image.
// If an error occurs, skip to the next image.
/* Always set up image event handlers before assigning the
SRC attribute to ensure that no events are missed. */
nextImage.onerror = preLoad;
nextImage.src =
adSet[adSet.current].getAttribute("src");
// The duration attribute specifies how long the image is
// displayed.
nextImage.duration =
adSet[adSet.current].getAttribute("duration");
if (null == nextImage.duration) // If not specified, use
nextImage.duration = 2000; // default 2 seconds.
if (++adSet.current == adSet.length)
adSet.current = 0; // Start over.
}
function skipImage() {
// Check whether next image has been downloaded.
if (nextImage.complete) {
document.all.ad.src = nextImage.src;
var duration = nextImage.duration;
preLoad();
window.tm = setTimeout(`skipImage()', duration);
}
else // Quickly iterate until image is available.
window.tm = setTimeout(`skipImage()', 10);
}
preLoad();
</SCRIPT>
</HEAD>
<BODY ONLOAD="window.tm = setTimeout(`skipImage()', 1);"
ONUNLOAD="clearTimeout(window.tm);">
<IMG ID="ad" SRC="ad4.gif" STYLE="border:2px solid navy">
</BODY>
</HTML>
Internet Explorer 4.0 also supports the construction of new images for background downloading using the new operator in addition to the createElement method. This operator is supported for compatibility with Netscape Navigator's JavaScript implementation. The new operator is a language-dependent technique for creating new elements. For example, in the preceding code, the line
nextImage = document.createElement("IMG");
can also be written as
nextImage = new Image();
However, because Netscape Navigator does not expose custom elements to scripts, the code for sequencing advertisements requires further modifications in order to run in Netscape Navigator: the information about the ad graphics needs to be stored by the script, most likely in an array, rather than in custom AdList elements.
Image Maps
Image maps specify different click regions on an image. The most common use for image maps is to create visual navigation maps. When the user clicks in a particular area of the image, the default action is to navigate the user to a specified page. Using the event model, you can override the default action with an alternative action.
Defining an Image Map
HTML provides two types of image maps: server-side and client-side. A server-side image map is specified simply by adding an ISMAP attribute to the image and creating an image map file on the server. When the user clicks on the image, the xy-coordinates are submitted to the server. The server-side image map has two inherent disadvantages: it generally requires a server round-trip, and it is not easily accessible because the click regions are not known to the browser or to scripts.
Client-side image maps use the Map element and have the advantage of not requiring a round-trip to the server. They also allow browsers to intelligently map and outline the click regions of the image. The Map element contains a set of Area elements that define the coordinates for each click region.
Map elements must be named in order to be associated with an image. Once the Map element is named, any number of images can be associated with it through the images' USEMAP attribute. The value for USEMAP must be specified as a link reference. For example, the following code associates an image with an image map named diagram:
<IMG SRC="diagram.gif" USEMAP="#diagram">
Client-side image maps and their syntax are demonstrated in the following examples. However, the complete syntax for defining a server-side or client-side image map is beyond the scope of this book. For details about image map syntax, refer to an HTML reference book or the Microsoft Web site (www.microsoft.com).
Image Maps and Events
You can place an image map anywhere in the document, independent of the image the map is associated with. Because multiple images can share a single image map, the Dynamic HTML object model maintains a special relationship between the image and its image map when firing events.
When an event is fired on an image map, the Area element receives the event, followed by the Map element, followed by the Image element the user clicked on. After the image receives the event, the event continues to bubble up through the image's parent elements. Thus, a single image map and events can be shared, or depending on the circumstances, the image itself can override or add its own behavior to the image map. Elements that contain the image map in the HTML source may never receive the events that originate in the image map.
Accessing the Image Map
An Image element's useMap property contains the name of the associated image map, prefixed with a # character. By removing the leading # character from the useMap property, you can access the image map. The useMap property is read/write, so it allows image maps to be dynamically associated with the image. The following code demonstrates a simple function for obtaining the associated image map from an Image element:
function getMap(elImage) {
// Be sure that a map is specified for the image.
if (null != elImage.useMap) {
// Remove the leading # from the bookmark.
var strMap = elImage.useMap.substring(1);
// Return the element with the specified name.
return document.all[strMap];
}
else
return null;
}
A useful application of dynamically changing an image map is to provide a different level of granularity in a complex image or geographic map. Figure 9-4 shows how a set of items--in this case, cities and states--can be made more manageable by letting the user first define a subset of items of interest. This filtering technique becomes even more powerful when used to distinguish between multiple overlapping regions.
Because the cities in this image overlap the states, the user might find it difficult to make a selection. By allowing the user to decide between cities and states, selection becomes much simpler. This filtering is easily implemented by toggling between two image maps for the image, depending on the user's selection, as shown in the following code.

Figure 9-4: An image that can use two different image maps.
Note: The coordinate lists in the Area elements cannot be broken onto multiple lines or the code will not run correctly. The lists are broken in the preceding code in order to fit them on the page; artificial line break symbols
) indicate line breaks that shouldn't appear in the actual code.
Accessing Area Elements
Dynamic HTML exposes the Area elements through the following collections:
| • | The links collection on the document |
| • | The all collection on the document |
| • | The areas collection on the Map element containing the Area elements |
Scripts can access the attributes of the Area element in any of these three ways in order to dynamically modify them. The Area element has an HREF attribute that contains a URL, and it exposes the same properties containing parts of that URL that the location and anchor objects expose. The areas collection provides the extra functionality of allowing new Area elements to be added and removed from the image map.
Dynamically modifying the coordinates and shapes within an image map is supported, but it is usually easier and more maintainable to define multiple image maps in the document and switch between them. The exception is when you can calculate the new click regions from the old by a simple transformation. For example, if an image can be scaled, it is easier to scale both the image and the image map. If a zoom function is supported on an image, any associated image map also needs to be zoomed with the image:
<HTML>
<HEAD>
<TITLE>Dynamically Scaling Image Maps</TITLE>
<SCRIPT LANGUAGE="JavaScript">
function getMap(elImage) {
// Be sure that a map is specified for the image.
if (null != elImage.useMap) {
// Remove the leading # from the bookmark.
var strMap = elImage.useMap.substring(1);
// Return the element with the specified name.
return document.all[strMap];
}
else
return null;
}
function zoomImage(elImage, amount) {
// Expand the image the specified amount.
var elMap = getMap(elImage);
elImage.width *= amount;
elImage.height *= amount;
// If an image map is available, scale it too.
if (null != elMap) {
for (var intLoop = 0; intLoop < elMap.areas.length;
intLoop++) {
var elArea = elMap.areas[intLoop];
// Break the coordinates string into an array.
var coords = elArea.coords.split(",");
var scaledCoords = "";
// Rebuild the new scaled string.
for (coord in coords) {
scaledCoords += (coords[coord] * amount) + ",";
}
// Put the scaled coordinates back into the map.
elArea.coords = scaledCoords;
}
}
}
function swapButtons(b1, b2) {
// Swap the enabled/disabled buttons.
document.all[b1].disabled = true;
document.all[b2].disabled = false;
}
</SCRIPT>
</HEAD>
<BODY>
<P>
<INPUT TYPE=BUTTON VALUE="Zoom In"
ONCLICK="zoomImage(document.all.img1, 2);
swapButtons(`zoomin', 'zoomout');"
ID="zoomin">
<INPUT TYPE=BUTTON VALUE="Zoom Out"
ONCLICK="zoomImage(document.all.img1, .5);
swapButtons(`zoomout', 'zoomin');"
ID="zoomout" DISABLED>
</P>
<P>
<IMG SRC="img001.gif" WIDTH=200 HEIGHT=200
ID="img1" USEMAP="#map1">
<MAP NAME="map1">
<AREA SHAPE="POLYGON"
COORDS="92, 140, 126, 114, 155, 139, 124, 163"
HREF="home.htm">
<AREA SHAPE="CIRCLE" COORDS="30, 105, 30" HREF="cool.htm">
<AREA SHAPE="RECT" COORDS="62, 28, 200, 79"
HREF="dhtml.htm">
</MAP>
</P>
</BODY>
</HTML>
Adding and Removing Area Elements
Using the areas collection, Dynamic HTML supports the ability to dynamically add and remove Area elements from an image map. The technique for creating a new Area element is the same as for creating a new image. The primary difference is that this new Area element can be added directly to an existing map's areas collection, whereas a new image object cannot be added to the document.
The areas collection exposes add and remove methods. The add method takes an Area element created with the createElement method and adds it to the areas collection. The remove method is used to remove an existing Area element from the image map. The following example is a simple image map editor written entirely in HTML:
<HTML>
<HEAD>
<TITLE>Image Map Editor</TITLE>
<SCRIPT LANGUAGE="JavaScript">
var curFocus = null;
function areaFocus() {
// Track the last Area element selected.
("AREA" == event.srcElement.tagName)
curFocus = event.srcElement;
}
function removeArea() {
// Remove an Area element.
var coll = document.all.dynaMap.areas;
if (null != curFocus) // Make sure one is selected.
// Loop over Area elements and find the one selected.
for (var intLoop = 0; intLoop < coll.length; intLoop++)
if (curFocus == coll[intLoop]) {
document.all.dynaMap.areas.remove(intLoop);
return;
}
alert("No Area element is selected.");
}
function addArea(f) {
/* Be sure that coordinates are specified. This code does
not perform any extra validation for the coordinates. */
if ("" != f.coordinates.value) {
var elArea = document.createElement("AREA");
elArea.coords = f.coordinates.value;
// Determine shape selected.
for (var intLoop = 0; intLoop < f.shape.length;
intLoop++)
if (f.shape[intLoop].checked)
elArea.shape = f.shape[intLoop].id;
document.all.dynaMap.areas.add(elArea);
}
else
alert("You need to enter a Coords value.");
event.returnValue = false;
}
</SCRIPT>
</HEAD>
<BODY>
<H1>Image Map Editor</H1>
<H2>Select a Shape</H2>
<FORM NAME="area">
<!-- The ID is used to determine the shape attribute. -->
<P>
<INPUT TYPE=RADIO NAME="shape" ID="rect" CHECKED>
<LABEL FOR="rect">Rect</LABEL>
<BR>
<INPUT TYPE=RADIO NAME="shape" ID="polygon">
<LABEL FOR="polygon">Polygon</LABEL>
<BR>
<INPUT TYPE=RADIO NAME="shape" ID="circle">
<LABEL FOR="circle">Circle</LABEL>
</P>
<P>
<LABEL FOR="coords">Coords</LABEL>
<INPUT TYPE=TEXT ID="coords" NAME="coordinates">
</P>
<P>
<INPUT TYPE=SUBMIT VALUE="Add Area"
ONCLICK="addArea(this.form)">
<INPUT TYPE=BUTTON VALUE="Remove Area"
ONCLICK="removeArea()">
</P>
</FORM>
<IMG SRC="img001.gif" WIDTH=200 HEIGHT=200 USEMAP="#dynaMap">
<MAP NAME="dynaMap" ONCLICK="areaFocus()">
</MAP>
</BODY>
</HTML>
Internet Explorer 3.0 supported a simple Marquee element for scrolling text horizontally. In Internet Explorer 4.0, the Marquee element was enhanced with a complete object model plus the ability to support and render any HTML code. This new Marquee control can even contain controls, which respond appropriately to mouse clicks and keyboard input as they move by. Other enhancements include the ability to scroll in any direction--left, right, up, or down.
A marquee can display one of three behaviors alternate, scroll, and slide. In alternate mode, the marquee's contents move back and forth or up and down, always remaining on the screen. In scroll and slide modes, the contents move in one direction. They may appear from the right marquee border, for example, and move left across the screen. In scroll mode, the motion does not repeat until after all of the contents have scrolled onto and off the marquee. In slide mode, the motion repeats sooner, after the last of the contents have scrolled onto the marquee. With any of these three behaviors, you can specify a finite number of repetitions or allow the marquee to continue animating until the user jumps to another page.
Marquee Animation Properties
The marquee attributes are exposed as properties that can be dynamically modified. For some of these properties, assigning a new value while the marquee is running causes the marquee to restart its animation; with others, it does not. The following table describes the attributes and how changing them affects the marquee.
| Attribute/Property | Restarts Marquee? | Description |
behavior | Yes | Specifies the alternate, scroll, or slide behavior for the marquee. The default value is scroll. |
direction | No | Specifies the direction of motion. All four directions are supported: left, right, up, and down. The default value is right. |
height | Yes | Specifies the physical height of the marquee. |
loop | Yes | Specifies the number of times for the animation to repeat. The default value is infinite. |
scrollAmount | No | Specifies the number of pixels to move each time the contents are redrawn. The default value is 6. |
scrollDelay | No | Specifies the number of milliseconds between times the contents are redrawn. The default value is 85. |
trueSpeed | No | Specifies whether the marquee should catch up with any skipped cycles. The default value is false, which causes the marquee to act as it does in Internet Explorer 3.0. |
width | Yes | Specifies the physical width of the marquee. |
Marquee Events
The Marquee element supports all the standard mouse and keyboard events. All elements contained within the marquee also continue to fire their respective events. The following table describes the events that the marquee exposes during the animation.
| Event | Description |
onstart | The marquee is about to begin scrolling. For a marquee in scroll or slide mode, this event fires each time a new animation sequence is about to be initiated. For a marquee in alternate mode, this event fires once at the beginning of the animation. |
onbounce | The marquee animation has reached the end and will reverse itself. This event fires when the Marquee's behavior property is set to alternate. |
onfinish | The marquee has finished scrolling. |
Marquee Methods
The Marquee element exposes two methods for starting and stopping the animation: start and stop. These methods can be used to manually control the scrolling of a marquee.
Using the stop and start methods, the following code allows the user to stop and start a marquee by holding down and releasing the mouse button over the marquee. By stopping the marquee, the user can read its contents more easily. The marquee's title attribute is displayed as a ToolTip when the mouse is held over the Marquee element.
<HTML> <HEAD> <TITLE>Marquee stop and start Methods</TITLE> </HEAD> <BODY> <MARQUEE TITLE="Hold down the mouse button to stop the marquee." ONMOUSEDOWN="this.stop();" ONMOUSEUP="this.start();"> <H1>Test Marquee</H1> <P>Clicking the mouse button and holding it down stops the marquee from scrolling.</P> <INPUT TYPE=BUTTON VALUE="Demo Button" ONCLICK="alert(`clicked');"> </MARQUEE> </BODY> </HTML>
The Object element allows you to include controls and applets that extend the browser. For example, you can create objects to embed graphs or even other documents directly into the document. An object may have its own properties, methods, and events, which the Object element exposes to scripts in the same way that it exposes its own members.
Handling Property Conflicts
A conflict can occur between the object's members and the members of a generic Object element. For example, if the object exposes an id property, it will collide with the id property exposed on the Object element. When this conflict occurs, referencing the id property references the element's version, not the object's. For referencing the object's version of the id property, all object elements expose an object property. This property returns access to the embedded object's members, as shown in the following code.
document.all.myObject.id // HTML element's id property document.all.myObject.object.id // Embedded object's id property
Alternative HTML
The Object element can contain HTML code that is displayed in browsers that do not support the Object element. The down-level contents are exposed as an altHTML property of the Object element in HTML.
The altHTML property can be used to provide contents to the user if the object fails to install. If the object fails to install, the alternative contents replace the object on the page. In the following code, the value of the Object element's altHTML property is the Paragraph element (the <P> and </P> tags and the text between):
<OBJECT CLASSID="java:myClass"> <PARAM NAME="color" VALUE="red"> <P> Either your browser does not support the Object element or an error occurred while downloading the object. </P> </OBJECT>
Object Events
An object can fire its own custom events. You can bind a handler to such an event using the <SCRIPT FOR= EVENT= > syntax or a language-dependent mechanism, but not using an attribute in the element's tag. The Object element exposes attributes for only those events that are predefined, not for events that the embedded object may fire.
Objects that expose standard events such as mouse and keyboard events can also take part in event bubbling. The object itself fires its standard event, followed by the browser firing the event on every parent element. Generic event handlers for standard events can test whether they originated in an object.
Tables are used in HTML for displaying tabular data and to provide greater control over the layout and position of elements in the document. Tables consist of rows; each row contains any number of cells. Dynamic HTML exposes a custom object model on tables that provides easy access to the underlying rows and cells within the table.
Tables were greatly enhanced in Internet Explorer 3.0 to support features that are now included in HTML 4.0. The THead, TBody, and TFoot elements were added to define the header, body, and footer sections of the table, and the Col and ColGroup elements provide greater control over columns. When used appropriately, these elements can improve the performance of the table, especially by defining the widths of the columns and providing more control over the rendering of borders. The Table element exposes a powerful object model for dynamically manipulating tables.
The table Object
Every Table element exposes rich information about its contents. The table object provides access to the three different sections of the table: THead, TBody, and TFoot. A table can have only one THead and TFoot but any number of TBody elements. Therefore, the object model exposes a single tHead and tFoot property and a tBodies collection. If a table does not explicitly define any sections, an implicit TBody element is created and added to the tBodies collection.
If the table happens to contain multiple THead or TFoot sections, the properties reference the first section encountered, and all remaining sections are exposed by the tBodies collection.
The table object exposes methods for creating and deleting THead, TFoot, and Caption elements. (There is currently no method to insert additional TBody elements into the table.) These methods are listed in the following table.
| Method | Description |
createTHead(), createTFoot(), createCaption() | Creates and returns the specified section if one does not exist. If the section already exists, rather than create another, the method returns the existing section. |
deleteTHead(), deleteTFoot(), deleteCaption() | Deletes the specified section and its rows from the table if the section exists. |
insertRow([index]) | Inserts a row into the table before the specified index. The row is added to the same section as the row currently specified by the index. If no index is specified, the row is added to the end of the table in the same section as the existing last row. This method returns the row that was inserted. |
deleteRow(index) | Deletes the row at the specified index from the table. |
The table object also exposes a rows collection. This rows collection represents every row in the table, independent of what section contains them. To determine what section contains a row, you can examine the parentElement property of the individual row. In addition, each section exposes a rows collection that represents the rows contained in that section.
The rows and cells Collections
The table object exposes the relationships between the table's rows and cells. As mentioned, the rows collection on the table object contains every TR element in the table, and the rows collections on the tHead, tBody, and tFoot objects contain the TR elements in their respective sections. Each row subsequently exposes a cells collection that references the TD or TH elements within the row. The rows and cells collections expose the same tags and item methods that are available on the other element collections. You can use an element's id property to look it up directly in a rows or cells collection.
Programming the rows Collection
The rows collection on the table object ignores whether a row is in the head, body, or foot of the table, but the TR element's relationship to its parent element is still maintained:
<TABLE ID="myTable"> <THEAD> <TR ID="header"><TH>City</TH><TH>State</TH></TR> </THEAD> <TBODY> <TR><TD>Issaquah</TD><TD>Washington</TD></TR> <TR><TD>Seattle</TD><TD>Washington</TD></TR> </TBODY> </TABLE>
In this example, the rows collection of myTable contains the three rows in the table. The parentElement property of an individual row can be examined to determine whether the row is inside a TBody or a THead element:
document.all.myTable.rows.length // 3 document.all.myTable.THead.rows.length // 1 document.all.myTable.rows[0].parentElement.tagName // THEAD document.all.myTable.rows[1].parentElement.tagName // TBODY
You can easily determine any row's position in the table. Three of the row's properties represent the row's zero-based index in the entire document, in the table, and in a section. The sourceIndex property represents the element's location in the document. This property, which all elements expose, is described in Chapter 8, "Scripts and Elements." The rowIndex property represents the index of the row in the entire table, and the sectionRowIndex property represents the index of the row in its section. In the previous example, the row containing Seattle has a rowIndex value of 2 and a sectionRowIndex of 1. (Its sourceIndex value depends on where the table appears in the document.)
Each row also provides access to its cells through a cells collection. The insertCell and deleteCell methods add and remove cells in the row. These methods work in the same manner as the insertRow and deleteRow methods. The insertCell method takes an optional parameter, the index of the cell before which the new cell is to be inserted, and returns the inserted cell. The deleteCell method takes the index of the cell to delete. The following code shows how to access and manipulate cells in the previous table:
document.all.myTable.rows[0].cells.length // 2 cells document.all.header.cells.length // 2 cells, accessed through the ID document.all.header.deleteCell(0); // Delete first cell in header row.
Each cell has a sourceIndex and a cellIndex property. The cellIndex property represents the index of the cell in the row.
The ROWSPAN and COLSPAN Attributes
The rows collections correspond to the HTML structure that defines the table. Therefore, even if a cell spans multiple rows, it is exposed only on the row that defines the cell. The following code demonstrates this relationship by flattening out access to a table that has a number of cells spanning multiple columns and rows:
<HTML>
<HEAD>
<TITLE>HTML Rows and Cells</TITLE>
</HEAD>
<BODY>
<TABLE BORDER ID="tbl1">
<CAPTION>Sample Table</CAPTION>
<TR><TD ROWSPAN=3>0, 0</TD>
<TD COLSPAN=2>0, 1</TD><TD>0, 2</TD></TR>
<TR><TD>1, 0</TD><TD ROWSPAN=2 COLSPAN=2>1, 1</TD></TR>
<TR><TD>2, 0</TD></TR>
</TABLE>
<SCRIPT LANGUAGE="JavaScript">
// Output information about the table above.
document.write("<H2>Table Information</H2>");
with (document.all.tbl1) {
for (var intRows=0; intRows < rows.length; intRows++)
document.write("Row " + intRows + " has " +
rows[intRows].cells.length + " cell(s).<BR>");
document.write("<P>Here is the same table without " +
"any cells spanning multiple rows or columns:");
document.write("<TABLE BORDER>");
for (var intRows = 0; intRows < rows.length; intRows++) {
document.write("<TR>");
for (var intCells = 0;
intCells < rows[intRows].cells.length;
intCells++)
document.write("<TD>" + intRows + "," + intCells +
"</TD>");
document.write("</TR>");
}
document.write("</TABLE>");
}
</SCRIPT>
</BODY>
</HTML>
Figure 9-5 displays the HTML representation of this table. The rows and cells are defined by the underlying source, independent of how the table is actually rendered. The numbers in a cell represent the index of its row in the rows collection, followed by the index of its cells in the cells collection. The second table provides a view of the table with the ROWSPAN and COLSPAN attributes removed. The corresponding cells have the same indexes in both tables.
You can modify the colSpan and rowSpan properties to dynamically change the table's layout. Changing these properties does not cause the rows or cells collections to change. The only way to affect the collections is to explicitly add or remove sections, rows, or cells from the table using the insert and delete methods.
The onresize Event
The table exposes an onresize event that is fired whenever the table is resized. This event fires when any cell changes in size. A script can change the size of a cell by changing its height or width property or by changing its contents. No matter how many cells may change in size due to a single action, the onresize event is fired only once on the table itself.
Global Style Sheets
In general, CSS is not inherited by the contents of a table cell. This fact follows from historical practice with regard to HTML formatting elements. For example, specifying a Font element around a table does not cause that font to be used by the table contents. When style sheets were introduced, this rule needed to be carried forward to ensure that existing pages did not break. Therefore, when style sheets are required on a table, they should be specified on the table or table cells directly to ensure that they are applied to the contents.
Creating a Calendar
The following code example demonstrates how to manipulate a table using the rows and cells collections. A script generates most of the document using the document.write method.
<HTML>
<HEAD>
<TITLE>Calendar</TITLE>
<STYLE TYPE="text/css">
today {color:navy; font-weight:bold}
days {font-weight:bold}
</STYLE>
<SCRIPT LANGUAGE="JavaScript">
// Initialize arrays.
var months = new Array("January", "February", "March",
"April", "May", "June", "July", "August", "September",
"October", "November", "December");
var daysInMonth = new Array(31, 28, 31, 30, 31, 30, 31, 31,
30, 31, 30, 31);
var days = new Array("Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday");
function getDays(month, year) {
// Test for leap year when February is selected.
if (1 == month)
return ((0 == year % 4) && (0 != (year % 100))) ||
(0 == year % 400) ? 29 : 28;
else
return daysInMonth[month];
}
function getToday() {
// Generate today's date.
this.now = new Date();
this.year = this.now.getYear() + 1900; // Relative
// to 1900
this.month = this.now.getMonth();
this.day = this.now.getDate();
}
// Start with a calendar for today.
today = new getToday();
function newCalendar() {
today = new getToday();
var parseYear = parseInt(document.all.year
[document.all.year.selectedIndex].text) - 1900;
var newCal = new Date(parseYear,
document.all.month.selectedIndex, 1);
var day = -1;
var startDay = newCal.getDay();
var daily = 0;
if ((today.year == newCal.getYear() + 1900) &&
(today.month == newCal.getMonth()))
day = today.day;
// Cache the table's tBody element named dayList.
var tableCal = document.all.calendar.tBodies.dayList;
var intDaysInMonth =
getDays(newCal.getMonth(), newCal.getYear() + 1900);
for (var intWeek = 0; intWeek < tableCal.rows.length;
intWeek++)
for (var intDay = 0;
intDay < tableCal.rows[intWeek].cells.length;
intDay++) {
var cell = tableCal.rows[intWeek].cells[intDay];
// Start counting days.
if ((intDay == startDay) && (0 == daily))
daily = 1;
// Highlight the current day.
cell.className = (day == daily) ? "today" : "";
// Output the day number into the cell.
if ((daily > 0) && (daily <= intDaysInMonth))
cell.innerText = daily++;
else
cell.innerText = "";
}
}
function getDate() {
// This code executes when the user clicks on a day
// in the calendar.
if ("TD" == event.srcElement.tagName)
// Test whether day is valid.
if ("" != event.srcElement.innerText)
alert(event.srcElement.innerText);
}
</SCRIPT>
</HEAD>
<BODY ONLOAD="newCalendar()">
<TABLE ID="calendar">
<THEAD>
<TR>
<TD COLSPAN=7 ALIGN=CENTER>
<!-- Month combo box -->
<SELECT ID="month" ONCHANGE="newCalendar()">
<SCRIPT LANGUAGE="JavaScript">
// Output months into the document.
// Select current month.
for (var intLoop = 0; intLoop < months.length;
intLoop++)
document.write("<OPTION " +
(today.month == intLoop ?
"Selected" : "") + ">" +
months[intLoop]);
</SCRIPT>
</SELECT>
<!-- Year combo box -->
<SELECT ID="year" ONCHANGE="newCalendar()">
<SCRIPT LANGUAGE="JavaScript">
// Output years into the document.
// Select current year.
for (var intLoop = 1995; intLoop < 2000;
intLoop++)
document.write("<OPTION " +
(today.year == intLoop ?
"Selected" : "") + ">" +
intLoop);
</SCRIPT>
</SELECT>
</TD>
</TR>
<TR CLASS="days">
<!-- Generate column for each day. -->
<SCRIPT LANGUAGE="JavaScript">
// Output days.
for (var intLoop = 0; intLoop < days.length;
intLoop++)
document.write("<TD>" + days[intLoop] + "</TD>");
</SCRIPT>
</TR>
</THEAD>
<TBODY ID="dayList" ALIGN=CENTER ONCLICK="getDate()">
<!-- Generate grid for individual days. -->
<SCRIPT LANGUAGE="JavaScript">
for (var intWeeks = 0; intWeeks < 6; intWeeks++) {
document.write("<TR>");
for (var intDays = 0; intDays < days.length;
intDays++)
document.write("<TD></TD>");
document.write("</TR>");
}
</SCRIPT>
</TBODY>
</TABLE>
</BODY>
</HTML>
The contents of the two combo boxes that provide the month and year lists are generated through script from internal arrays that track the months and days available to the calendar. The code also ensures that the current month and year are initially selected when the document loads. The table that defines the calendar is itself generated by a script that generates the 42 cells using two nested loops. Once the page is loaded, the newCalendar function is called and automatically walks through and fills in the cells of the table's tBody element with the current month's calendar.
Figure 9-6 shows the calendar example in action.
This example also includes a simple click event handler that executes when the user clicks on any date in the calendar. Currently the handler does nothing more than display the date the user clicked, but it demonstrates how the calendar can be easily extended to be more interactive and useful to an application.