Javascript: The Definitive Guide

Previous Chapter 14 Next
 

14. Documents and Their Contents

Contents:
The Document Object
The Link Object
The Anchor Object
The JavaObject Object

14.1 The Document Object

If the Window object, which represents a window or a frame, is the central object in client-side JavaScript, then the Document object, which represents the contents of a window or frame, runs a close second, and is just as commonly used. This object has properties that specify information about the document: the URL, its last-modified date, the URL of the document that linked to it, the colors that it is displayed in. The Document object also has a few methods that allow JavaScript programs to dynamically output text into a document, and to dynamically create new documents from scratch. Finally, the Document object also contains a number of array properties that specify information about the contents of the document. These arrays contain objects that represent the links, anchors, HTML forms, applets, and embedded data contained in the document. These arrays and the objects they contain are very important in JavaScript programming, and will be described in their own sections later in this chapter.

Document Properties

The Document object has a number of properties that correspond to attributes of the <BODY> tag, and which are used to specify the colors that the document is displayed in. The bgColor property, and the BGCOLOR attribute specify the background color of the document. Similarly, the fgColor and the TEXT attribute specify the default color for text in the document. The linkColor property specifies the color of unvisited links, and vlinkColor and alinkColor[1] specify the color of visited links and of activated links (i.e., links currently being clicked on). The LINK, VLINK, and ALINK attributes correspond to these properties.

[1] You can set the alinkColor property in Internet Explorer, but it will be ignored, since IE never displays a separate color for activated links.

These color properties of the Document object are read/write properties, but they can only be set before the <BODY> tag is parsed. You can set them dynamically with JavaScript code in the <HEAD> of a document, or you can set them statically as attributes of the <BODY> tag, but you cannot set them elsewhere.

The exception to this rule is the bgColor property. You can set this property at any time, and doing so will cause the background color of the browser to change. Unfortunately, on Unix platforms, changing the background color can make the contents of the page disappear (usually until the window is scrolled or otherwise redrawn). Setting the background color can still produce a useful special effect when done with small, empty frames, however.

Each of these color properties has a string value. To set a color, you can use one of the predefined color names listed in Appendix G, JavaScript and HTML Color Names and Values, or you can specify the color as red, green, and blue color values, expressed as a string of six hexadecimal digits in the form "RRGGBB".

The Document object also has properties that are somewhat more interesting than these color properties. For the most part, the values of these other properties are derived from the HTML content of the document or from HTML headers supplied by the web server. As we saw in the discussion of the Location object, the Document object has a location property (and a URL property which is a preferred synonym in Navigator 3.0) that specifies the URL of the document. Because of redirection performed by the web server, this URL may be different than the requested URL.

The lastModified property is a string that specifies the date and time of the most recent change to the document. This is a value supplied by some, but not all, web servers. The referrer property specifies the URL of the document that contained the hypertext link that the user clicked on to get to the current document. If the current document was loaded by explicitly typing a URL, then this property will be empty. Note that this property is not supported in Internet Explorer 3.0. Finally, the title property contains any text that appears between the <TITLE> and </TITLE> tags in the <HEAD> of the document. You cannot use this property, of course, in code that appears before the <TITLE> of a document.

A simple use for the lastModified property is to automatically include a timestamp in your documents, so that users know whether the information they are seeing is up to date. You can do this by including HTML and JavaScript code like the following at the bottom of all your documents. Note that this code displays the document title and URL as well as its modification date:

<HR><FONT SIZE=1>
Document: <I><SCRIPT>document.write(document.title);</SCRIPT></I><BR>
URL: <I><SCRIPT>document.write(document.URL);</SCRIPT></I><BR>
Last Update: <I><SCRIPT>document.write(document.lastModified);</SCRIPT></I>
</FONT>

A possible use for the referrer property is to save this value in a hidden field of a form on your web page. When the user submits the form (for whatever reason your page contains the form in the first place) you can save this referrer data on the server. This will allow you to analyze what links exist to your page, and also what percentage of hits come through which links. Another use of this property is a trick to prevent unauthorized links to your page from working correctly. For example, if you only want users to be able to get to your page through links in pages from one particular site, you might use code like this at the top of your page:

<SCRIPT>
if (document.referrer == "" || document.referrer.indexOf("mysite.com") == -1)
    window.location = "javascript:'You can't get there from here!'";
</SCRIPT>
Don't consider this trick to be any kind of serious security measure, of course. Anyone determined to read your pages could simply disable JavaScript in their browser, and then load the page.

The write() Method

Without a doubt, the most important feature of the Document object (and perhaps of client-side JavaScript in general) is the write() method, which allows us to dynamically generate web page content from our JavaScript programs. There are several ways that this method can be used. The most obvious is to use it within a script to output HTML into the document that is currently being parsed. This is the way it was used above to display the Document lastModified property at the bottom of the web page. Be aware that you can only output HTML to the current document while that document is being parsed. That is, you can only call document.write() from within <SCRIPT> tags, because these scripts are executed as part of the document parsing process. In particular, if you call document.write() from an event handler, you will end up overwriting the current document (including its event handlers), instead of appending text to it.

Although you can't usefully write to the current document from an event handler, there is no reason you can't write to a document in another window or frame, and doing so can be a very useful technique for multiwindow or multiframe web sites. For example, JavaScript code in one frame of a multiframe site might display a message in another frame with code like this:

<SCRIPT>
parent.frames[0].document.open();
parent.frames[0].document.write("<HRE>Hello from your sibling frame!<HR>");
parent.frames[0].document.close();
</SCRIPT>

We previously saw code that dynamically creates an HTML document like this in Example 12-2 and Example 12-6. Recall that to create a new document, we first call the open() method of the Document object, then call write() any number of times to output the contents of the document, and finally call the close() method of the Document object to indicate that we are complete. This last step is important--if you forget to close the document, the browser will not stop the "document loading" animation it displays. Also, the browser may buffer up the HTML you have written, and is not required to display it until you explicitly end the document by calling close().

In contrast to the close() call, which is required, the open() call is optional. If you call the write() method on a document that has already been closed, then JavaScript implicitly opens a new HTML document, as if you called the open() method. This explains what happens when you call document.write() from an event handler within the same document--JavaScript opens a new document. In the process, however, the current document and its contents, including scripts and event handlers, is discarded. In Navigator 3.0, this causes surprising programming difficulties and unexpected error messages. In Navigator 2.0, it can actually cause the browser to crash. The best rule of thumb is that a document should never call write() on itself from within an event-handler.

A couple of final notes about the write() method. First, many people do not realize that the write() method can take more than one argument. When you pass multiple arguments, they will be output one after another, just as if they had been concatenated. So instead of writing:

document.write('Hello, '  + name + " Welcome to my home page!");
you can equivalently write:

document.write('Hello, ', name, " Welcome to my home page!");

The second point to note about the write() method is that the Document object also supports a writeln() method, which is identical to the write() method in every way, except that it appends a newline after outputting its arguments. Since HTML ignores linebreaks, this newline character usually doesn't make a difference, but, as we'll see in a bit, the writeln() method can be convenient when working with non-HTML documents.

Flushing Generated Output

When you use the write() method to dynamically generate HTML output, the text you write may not appear in the browser window right away. The contents of your individual write() calls may be buffered up so that they can be written out to the document in larger chunks. Unfortunately, there is no flush() method of the Document object that forces all output to appear. Instead, you must know the necessary tricks to make your output appear.

Calling the close() method is the simplest technique for forcing your output to be displayed, of course. Sometimes, though, you want intermediate output to be displayed, and are not yet ready to close the document you are generating. In this case, there are two techniques for flushing output. In Navigator 3.0, output is flushed whenever a new line is forced in the browser. Thus, if you output a <BR> or <P> or <HR> tag, all the text before that tag will appear. In Internet Explorer 3.0, it is not so easy, however--your output does not appear until the current <SCRIPT> block ends or the current event handler function returns. Thus, for this browser, you may need to break your code up into smaller chunks in order to assure that output is correctly flushed. Note that you can always use setTimeout() to schedule the next "chunk" of code to run in 0 milliseconds. This technique allows control to temporarily return to IE so that it can display any pending output.

Non-HTML Documents

When you open a new document with the open() method, the browser assumes that you'll be creating an HTML document. But this is not necessarily the case. Web browsers can display a number of other data formats besides HTML text. When you want to dynamically create and display a document using some other data format, you call the open() method with a single argument, which is the MIME type you desire. Note that while this technique is supported in Navigator 2.0 and 3.0, it does not work in Internet Explorer 3.0--in that browser, any argument passed to open() is ignored.

The MIME type for HTML is "text/html". The most common format other than HTML is plain text, with a MIME type of "text/plain". If you want to use the write() method to output text that uses newlines, spaces, and tab characters for formatting, then you should open the document by passing the string "text/plain" to the open() method. Example 14-1 shows one way you might do this. It implements a debug() function that you can use to output plain-text debugging messages from your scripts into a separate window that appears when needed. Figure 14-1 shows what the resulting window looks like.

Example 14-1: Creating a Plain-Text Document

<SCRIPT>
var _console = null;
function debug(msg) 
{
    // Open a window the first time we are called, or after an existing
    // console window has been closed.
    if ((_console == null) || (_console.closed)) { 
        _console = window.open("","console","width=600,height=300,resizable");
        // open a document in the window to display plain text
        _console.document.open("text/plain");
    }
    _console.document.writeln(msg);
}
</SCRIPT>
<!-- Here's an example of using this script -->
<SCRIPT>var n = 0;</SCRIPT>
<FORM>
<INPUT TYPE="button" VALUE="Push Me"
       onClick="debug('You have pushed me:\t' + ++n + ' times.');">
</FORM>

This technique of using non-HTML documents is not limited to plain-text documents, or to textual documents in general. It can also be used with images, for instance. If we open a document and specify the MIME type "image/xbm", for example, then the browser will expect the contents of that document to be an image in XBM format. Because XBM images have an ASCII representation, we can easily write a static XBM image to the document, or even generate a dynamic image on the fly (perhaps using a Java applet to do the image processing, for speed). Example 14-2 shows how you can create an "image/xbm" document with a static XBM image, and also shows how this XBM image can be used for image embedded in an HTML document. Figure 14-2 shows the windows created by the example. This technique would be much more efficient and interesting if it used a compact image format like "image/gif". Unfortunately, this is not possible because GIF images use a binary format that includes NULL characters (i.e., the byte 0) and the current versions of JavaScript cannot output this character.

Example 14-2: Generating XBM Images with JavaScript

<SCRIPT>
// This is a long string in XBM image format. It defines an image.
// This is an ASCII format, which means we can easily manipulate it 
// in JavaScript, but also means that it is not compact. This is only
// a 22x22 pixel image. The real power of this technique comes, of course
// when we start generating XBM data dynamically at run-time instead of
// using a static string as we do here.
image_text = 
"#define plaid_width 22\n" +
"#define plaid_height 22\n" +
"#define plaid_x_hot -1\n" +
"#define plaid_y_hot -1\n" +
"static char plaid_bits[] = {\n" +
"  0x75, 0xfd, 0x3f, 0xaa, 0xfa, 0x3e, 0x75, 0xfd, 0x3f, 0xaa, 0xfa, 0x3e,\n" +
"  0x75, 0xfd, 0x3f, 0xff, 0x57, 0x15, 0x75, 0xfd, 0x3f, 0xaa, 0xfa, 0x3e,\n" +
"  0x75, 0xfd, 0x3f, 0xaa, 0xfa, 0x3e, 0x75, 0xfd, 0x3f, 0x20, 0xa8, 0x2b,\n" +
"  0x20, 0x50, 0x15, 0x20, 0xa8, 0x2b, 0x20, 0x50, 0x15, 0x20, 0xa8, 0x2b,\n" +
"  0xff, 0xff, 0x3f, 0x20, 0xa8, 0x2b, 0x20, 0x50, 0x15, 0x20, 0xa8, 0x2b,\n" +
"  0x20, 0x50, 0x15, 0x20, 0xa8, 0x2b};\n";
// Here we create a new window, open the document, specifying a MIME type of
// image/xbm, and then output the image text. The window will display
// the XBM data we give it.
win1 = window.open("", "win1", "width=100,height=100,resizable");
var d = win1.document;
d.open('image/xbm');
d.write(image_text);
d.close();
// There are also a couple of other ways to use XBM image data that do not
// involve specifying a MIME type when opening the document. Here we 
// create a new window, and then use a javascript: URL as the SRC of an
// inline <IMG>. This is an XBM image embedded in a text/html document,
// so we can display text, anchors, etc.
win2 = window.open("", "win2", "width=100,height=100,resizable");
var d = win2.document;
d.open();
d.write('<B>Plaid:</B><BR>');
d.write('<A HREF="javascript:self.close();">');
d.write('<IMG SRC="javascript:opener.image_text" WIDTH=22 HEIGHT=22>');
d.write('</A>');
d.close();
// We can also use the javascript: URL with the BACKGROUND tag of the
// <BODY> tag. XBM is a black-on-white image format, but note how the
// BGCOLOR tag can replace the white background.
win3 = window.open("", "win3", "width=100,height=100,resizable");
var d = win3.document;
d.open();
d.write('<BODY BACKGROUND="javascript:opener.image_text" BGCOLOR="red">');
d.close();
</SCRIPT>


Previous Home Next
The History Object Book Index The Link Object