In this chapter, you learn how to create an outlined document, a document that includes an outline describing its logical structure. You can use such an outline both to get an overview of the document and to skip quickly to various parts of the document. This chapter shows you how to create an outlined document on the World Wide Web. First you create a simple outlined document without JavaScript. Then you use JavaScript to build a much more sophisticated outlined document that is always visible on screen and that you can collapse or expand to see in less or greater detail.
You want to put a document on the World Wide Web; the document includes an outline describing its logical structure. The ideal candidate for an outlined document is one that has a logical structure that fits into an outline. You should also be able to read the document piecemeal, and possibly in random order; the outline will help you jump directly to the portion of the document you want to read. Examples include Frequently Asked Questions (FAQs), legal documents, and directories. The example in this chapter is a well-organized set of bookmarks to other Web sites, based on my own bookmark collections.
There are five requirements for putting together your outline.
A conventional approach to the problem would be to create a single document that contains the document outline. The document outline would consist of links to the Web pages that make up the document. The links would be arranged as items in a list, as shown in Listing 5.1.
Listing 5.1: A document outline without JavaScript
<HTML> <HEAD> <TITLE>Marc Johnson's Roadmap</TITLE> </HEAD> <BODY> <H1>Marc Johnson's Roadmap</H1> <UL> <LI><A HREF="index.html">My home page</A> <LI><A HREF="amusement.html">Amusements</A> <UL> <LI><A HREF="amusement.html#Comics">Comics</A> <LI><A HREF="amusement.html#Funnies">Funnies</A> <LI><A HREF="amusement.html#Games">Games</A> <LI><A HREF="amusement.html#Media">Media</A> <LI><A HREF="amusement.html#Travel">Travel</A> </UL> <LI><A HREF="business.html">Business</A> <UL> <LI><A HREF="business.html#Book Stores">Book Stores</A> <LI><A HREF="business.html#Other">Other</A> </UL> <LI><A HREF="internet.html">Internet</A> <UL> <LI><A HREF="internet.html#Providers">Providers</A> <LI><A HREF="internet.html#Resources">Resources</A> <LI><A HREF="internet.html#Surfing">Surfing</A> <LI><A HREF="internet.html#Web Pages">Web Pages</A> <UL> <LI><A HREF="internet.html#Graphics">Graphics</A> <LI><A HREF="internet.html#HTML">HTML</A> <LI><A HREF="internet.html#Icons">Icons</A> <LI><A HREF="internet.html#Pages">Pages</A> <LI><A HREF="internet.html#Tools">Tools</A> </UL> </UL> <LI><A HREF="reference.html">Reference Materials</A> <UL> <LI><A HREF="reference.html#Books">Books</A> <LI><A HREF="reference.html#Documents">Documents</A> <LI><A HREF="reference.html#Education">Education</A> <LI><A HREF="reference.html#Government">Government</A> <LI><A HREF="reference.html#Jobs">Jobs</A> <LI><A HREF="reference.html#Reference Servers">Reference Servers</A> </UL> <LI><A HREF="sf.html">Science Fiction</A> <UL> <LI><A HREF="sf.html#Books">Books</A> <LI><A HREF="sf.html#Miscellaneous Television">Miscellaneous Television</A> <LI><A HREF="sf.html#Star Trek">Star Trek</A> <LI><A HREF="sf.html#World Designs">World Designs</A> </UL> <LI><A HREF="software.html">Software</A> <UL> <LI><A HREF="software.html#Academia">Academia</A> <LI><A HREF="software.html#AandD">Analysis and Design</A> <LI><A HREF="software.html#Linux">Linux</A> <LI><A HREF="software.html#Programming">Programming</A> <LI><A HREF="software.html#Shareware">Shareware</A> <LI><A HREF="software.html#Vendors">Vendors</A> </UL> <LI><A HREF="weather.html">Weather</A> <UL> <LI><A HREF="weather.html#Images">Images</A> <LI><A HREF="weather.html#NWS Bulletins">NWS Bulletins</A> <LI><A HREF="weather.html#Weather Servers">Weather Servers</A> </UL> </UL> <HR> </BODY> </HTML>
Figure 5.1 shows (partially) what the HTML code in Listing 5.1 produces. This document meets requirement 3. That is, it helps users navigate the various Web pages the document consists of; they can do this simply by clicking on links in the outline. It also meets requirement 4-in other words, it lets you quickly navigate the document outline. Scrolling through the outline looking for the right link is much faster than scrolling through the document itself and following links and waiting for them to load. There is no specific limit to how deeply you can nest UL elements, so the design of this document meets requirement 5; it is extensible and reusable. The fact that the document will probably be divided into several Web pages is also not a problem. This is the kind of thing that conventional Web pages can easily handle with ordinary links-so the document meets requirement 1.
Figure 5.1 : A document outline.
There is still requirement 2 remaining-this is the requirement that the user can access the document outline easily from any point on any of the constituent Web pages. If you're not using JavaScript, there's not a very good solution here. About the best you can do is to insert links back to the document outline page. You have to insert links into each Web page in the document, which can be a problem if you don't have direct control over all of the constituent Web pages. Alternatively, you can simply assume that the user can use the browser's back button to return to the outline. This solution is a maintenance headache and highly dependent on user cooperation.
Although you can create a workable outline document without JavaScript, JavaScript can improve upon the solution, if you put in a little work. With JavaScript, you can keep the outline visible while the user reads the document, and you can make the outline much easier to navigate.
You will better meet requirement 2-the requirement that users can access the document outline easily from any point on any of the constituent Web pages-if the document outline is always visible. You can do this by creating a frame document, splitting the window in two. While this does steal some room from the document pages themselves, the loss is minimal, as most Web pages don't fit within the browser window anyway. The following code puts the document outline on the left and the current document page on the right:
<HTML> <HEAD> <TITLE>Marc's List of Great Web Pages</TITLE> </HEAD> <FRAMESET COLS="30%,*"> <FRAME NAME="outline" SRC="outline.htm"> <FRAME NAME="contents" SRC="main.htm"> </FRAMESET> </HTML>
Now the document outline is next to the current page of the document, as you can see in Figure 5.2. You don't have to insert links to the document outline into each document Web page. The document outline is much easier to get to than in the non-JavaScript solution. You don't have to hunt around to find it-it's always there on display.
Figure 5.2 : A frame document.
Using JavaScript, you can also make it easier for the user to quickly navigate the document outline. You can allow the user to hide irrelevant portions of the outline, leaving only the portions that are important to him or her. You can do this by making the outline a dynamic document created on the fly instead of a static document loaded from the server.
You can visualize the document outline shown in Figure 5.1 as a tree structure. With JavaScript, you can create a new-and-improved tree structure whose branches can be collapsed so that the subordinate branches and leaves are out of the way. This approach allows the user to "prune" the tree on the fly, leaving a shorter outline that's easier to navigate. Naturally, you should be able to bring back the subordinate branches and leaves at any time. As an example, Figure 5.3 shows a full outline; Figure 5.4 shows the same outline with the Web Pages branch collapsed.
Figure 5.3 : The Web Pages branch is open.
Figure 5.4 : The Web Pages branch has been collapsed and is closed.
First you'll create a simple tree structure consisting of two kinds of objects: branch objects and leaf objects. Branch objects have zero or more subordinate leaf objects and zero or more subordinate branch objects. Leaf objects have no constituent objects.
A branch object has a title property; this is the text that will be displayed in the document outline. It also has a flags property that, if set, indicates that its constituent branch and leaf objects are to be displayed. If the flags property is clear, its constituent objects are not to be displayed. A branch object also has a url property, which is the URL of a document that describes the branch and its constituent objects-a summary, perhaps. The url property may be empty if there is no document logically associated with the branch and its constituent objects.
A leaf object also has a title property and a url property. The url property should not be empty; the leaf should point to a document. As with the branch object, you can use the title and url properties to create a link (<A HREF="...">...</A>) element.
The branch and leaf objects are similar enough that you can effectively combine them into a single kind of object, called a node object. The entire document outline can then be represented as a list of node objects, which greatly simplifies the tasks of initializing and displaying the outline.
You also need some way to indicate, within the list of nodes, that you've listed all of the constituent objects of a given branch. One way is to mark the last constituent node object with a special flag. There are two problems with this approach. First, when you add new branches and leaves to the tree, you may accidentally place the new nodes after the end-of-branch flag. If you do, the new nodes will appear to be a constituent of the wrong branch, or may not even appear to belong to another branch. Second, there is a problem when a node object is the last object in more than one branch, such as the internet.html#Tools link in Listing 5.1. You either have to somehow represent the fact that a node marked as end-of-branch may terminate more than one branch (and indicate which ones) or mark the node's branch as the termination of its parent branch by applying an end-of-branch marker to it. Either way, it's needlessly messy and not very intuitive to the person maintaining the document outline.
The solution I chose was to create a special empty node object that follows the last node of a branch, similar to the </UL> tag that terminates a <UL> list element-it's a little harder to overlook an explicitly defined node than a node that happens to be a leaf or branch (like most of the nodes) and also the end of a branch.
So now you have a node object. It has a flags property, which is used to contain the information about whether the node is a branch, a leaf, or an empty node. The flags property can also indicate whether the branch is open (constituent objects displayable) or closed (constituent objects suppressed). It has a title property, which empty node objects don't use. It has a url property, which is also used only by leaf and branch nodes. There is also one more property, the index property, which contains the index of the node object into the array of node objects. You'll use this property later to close and open branch objects.
There's one more thing you need for your node object: a method for it to display itself. This way, you can display the document outline by iterating over an array of node objects. So you'll add a drawNode method. When you create a new node object, you'll set the drawNode method to a function designed to draw a branch node, a leaf node, or an empty node. Here's what the function that creates a new node object looks like:
function node(flags, title, url) { this.index = nodeCtr; this.flags = flags; if (flags == __leaf__ || ((flags & __branch__) == __branch__)) { this.title = title; if (flags == __leaf__) { this.url = url; this.drawNode = drawLeaf; } else { if (node.arguments.length >= 3) { this.url = url; } else { this.url = ""; } this.drawNode = drawBranch; } } else { this.drawNode = drawEmptyNode; } }
In plain English, here's what happens in the node function:
First, the node's index property is set to the value of a variable called nodeCtr; this variable is explained in the section "Creating Arrays of Depth and Node Objects" later in this chapter.
Next, the node's flags property is set to the value of the flags parameter. The flags value is a bitmap, which is a number made up of individual binary digits, as you can see in Figure 5.5. Bitmaps are useful for expressing sets of Boolean (yes/no, true/false) values without using a lot of different variables.
Figure 5.5 : The bitmap structure.
Then the flags value is tested. If the flags value indicates that the node is a leaf or a branch, one block of code is executed; otherwise, the node must be an empty node. Note that the test for the node being a branch is different from the test for it being a leaf-a branch node's flags value also indicates whether it's open. Rather than test for leaf or open branch or closed branch, you test for leaf, or "has branch bit set."
Once it's determined that the node is either a leaf or a branch, the title property is set to the value of the title parameter. Then the code further differentiates between a leaf or a branch.
If the node is a leaf, the node's url property is set to the value of the url parameter and the drawNode method is set to the drawLeaf function.
If the node is not a leaf (and is therefore a branch), the code checks how many parameters were passed. If there are at least three arguments (node.arguments.length >= 3), a url was passed and the node's url property is set to the url parameter's value. Otherwise, the url property is set to an empty string. The node's drawNode method is set to the drawBranch function.
The empty node initialization is simple: The node's drawNode method is set to the drawEmptyNode function.
You need something else to make the tree display work: something to keep track of how deep into the tree you are. As you traverse the tree, you may be going up and down branches. To remember whether, at a given depth, you're supposed to display a node's contents, you need an object that stores, at a given depth, whether or not you're supposed to display objects.
I call this object a depth object. It has two properties: saved and value. The value property contains a zero value if the objects at or below this depth are not to be displayed and a nonzero value if the objects are to be displayed. The saved property keeps track of the previous value of the value property.
There are two methods: open() and close(). The open() method is called when a branch object is encountered in the list of nodes. It copies the depth object's value property to its saved property and sets the depth object's value property to a value corresponding to the __open__ bit in the branch object's flags property. The close() method is called when an empty object is encountered in the list of nodes. It simply restores the depth object's value property from its saved property. The depth function that defines a new depth object sets the saved and value properties to zero (not displayed), the open method to the depthOpen function, and the close method to the depthClose function. The depth functions look like this:
function depthOpen(newValue) { this.saved = this.value; this.value = newValue; } function depthClose() { this.value = this.saved; } function depth() { this.saved = Ø; this.value = Ø; this.open = depthOpen; this.close = depthClose; }
You need an array of depth objects to keep track, at each level of the outline, of whether objects are to be displayed; its length is the depth of the deepest branch node in the nodes array. In the example shown here, you can go three branches deep, so there need to be three depth objects. To create the array, you will use a function described in Netscape's online documentation (see the Quick Reference), MakeArray:
function MakeArray(n) { this.length = n; for (var i = 1; i <= n; I++) { this[ i ] = Ø; } return this; }
You then create the depthList and nodeList variables:
var depthList = new MakeArray(3); var nodeList = new MakeArray(53);
You need to initialized the depth and node objects after creating the arrays. For the depth objects, this is very straightforward:
for (var n = 1; n <= 3; n++) { depthList[ n ] = new depth(); }
For the nodeList objects, the process is a bit more involved. To make it a little easier, I created a counter variable, which you saw earlier in the node object constructor function:
var nodeCtr = Ø; nodeCtr++; nodeList[ nodeCtr ] = new node(...); nodeCtr++; nodeList[ nodeCtr ] = new node(...); . . .
If you use this strategy, it's harder to miss a node or to initialize a node twice. At the end of the initialization sequence, you can also check your work. The following code, which is executed after the last node is created, verifies that nodeCtr agrees with the nodeList array's length property. If the two values disagree, construct a string that describes the error and put it on the screen in an alert box:
if (nodeCtr != nodeList.length) { alert("Node counter (" + nodeCtr + ") does not match nodeList.length (" + nodeList.length + ")"); }
You can remove or, better, comment out this check after you've tested the pages and verified that you did not see this alert window.
In initializing the nodeList, it's also a good idea to indent objects when you encounter a branch object, like so:
nodeList[ nodeCtr ] = new node(__branch__, ... nodeList[ nodeCtr ] = new node(__leaf__, ... nodeList[ nodeCtr ] = new node(__leaf__, ... nodeList[ nodeCtr ] = new node(__empty__, ...
This way, you have a visual check that you've balanced the branch and empty nodes correctly, and that branch node and empty node pairs contain the correct objects. You'll also perform a check when you draw the tree, as you'll see later in this chapter.
So now you have your new objects fairly well defined, except for the drawNode functions (drawBranch, drawLeaf, and drawEmptyNode). Let's take a look at them.
Drawing empty nodes The drawEmptyNode function is called when the node is empty. The empty node draw method is simple:
function drawEmptyNode() { depthList[ parent.toc.level ].close(); parent.toc.level--; }
Now where did parent.toc.level come from? Well, the actual document that will display the document outline does not change, nor does the function used in it to draw the tree. The function used to draw the tree needs to keep track of how many levels deep it is. Therefore, it's logical for the variable that maintains the depth to be kept in that document. You can access a global variable in another document by including its parentage-parent.toc in this case. This tells you that the level variable is maintained in the document that is displayed in the frame named toc, and that the frame toc is subordinate to this frame's parent window or frame.
Drawing leaf nodes The drawLeaf function, which is called when the node is a branch, is a little more complicated than the drawEmptyNode function:
function drawLeaf() { for (var j = Ø; j < parent.toc.level; j++) { if (depthList[ j + 1 ].value == Ø) { return; } } for (var k = Ø; k < parent.toc.level; k++) { parent.toc.document.write("    "); } parent.toc.document.write("<A HREF=\"javascript:parent.outlineData.setContents('" + this.url + "');\"" + gt + this.title + "</A" + gt); drawLineBreak(); }
This code first checks the depth objects that dictate whether objects at this level or above are to be displayed. If any of the depth objects indicate that the objects at or below their level are not to be displayed, the code simply returns-there's nothing to do.
Once it's established that you will be drawing something, you write four nonbreaking space characters to the toc frame's document, and you do this for each level that we need to indent. Nonbreaking spaces are treated specially by the browser, which does not collapse them into a single space as it does normal space and tab characters.
Finally, you create a link for the leaf. You can't use the leaf's url property itself as the HREF parameter; you need to make the document appear in the other window. To do that, the setContents function takes the url property as an argument and sets the location of the contents frame with it:
function setContents(url) { parent.contents.location = url; }
And where is this outlineData frame? You can't see it, but it's used to contain the data to be displayed in the document outline, and it contains the node and depth functions. This keeps the data away from view and encapsulates it so that it can be modified or swapped out without changing the rest of the documents.
To make the outlineData frame invisible, you modify the main document slightly:
<HTML> <HEAD> <TITLE>Marc's List of Great Web Pages</TITLE> </HEAD> <FRAMESET ROWS="1ØØ%,*"> <FRAMESET COLS="3Ø%,*"> <FRAME NAME="toc" SRC="empty.htm"> <FRAME NAME="contents" SRC="empty.htm"> </FRAMESET> <FRAME NAME="outlineData" SRC="data.htm"> </FRAMESET> </HTML>
The outer FRAMESET element allocates 100 percent of the window to the inner FRAMESET. The remaining space (even though there isn't any) is allocated to the outlineData frame, which is invisible. You also use the empty.htm url now for the contents of the toc frame. You're going to redraw the outline on the fly, so you only need a placeholder until you first draw the document outline.
Finally, you may have noticed the gt variable used in the creation of the link. This is a variable that contains a > character, hiding it from ignorant browsers:
var gt = unescape("%3E");
Finally, drawLeaf calls drawLineBreak, which looks like this:
function drawLineBreak() { parent.toc.document.write("<BR" + gt); }
This forces a new line for the next node.
The final result of drawLeaf looks like this:
<A HREF="javascript:parent.outlineData.setContents('index.html')">Home Page</A>
Drawing branch nodes The drawBranch function, which is called when the node is a branch, is a little more complicated than the drawLeaf function:
function drawBranch() { for (var j = Ø; j < parent.toc.level; j++) { if (depthList[ j + 1 ].value == Ø) { parent.toc.level++; return; } } for (var k = Ø; k < parent.toc.level; k++) { parent.toc.document.write("    "); } parent.toc.document.write("<A NAME=\"line" + this.index + "\" HREF="javascript:parent.outlineData.clickOnBranch(" + this.index + ")\"" + gt); if (this.flags & __open__) == Ø) { parent.toc.document.write("[+]"); depthList[ parent.toc.level + 1 ].open(Ø); } else { parent.toc.document.write("[-]"); depthList[ parent.toc.level + 1 ].open(Ø); } parent.toc.document.write("</A" + gt + " "); if (this.url.length == Ø) { parent.toc.document.write(this.title); } else { parent.toc.document.write("<A HREF=\"javascript:parent.outlineData.setContents('" + this.url + "');\"" + gt + this.title + "</A" + gt); drawLineBreak(); parent.toc.level++; }
Like the drawLeaf method, drawBranch checks the depthList array to see if there's anything to draw. If there isn't, it increments parent.toc.level anyway, so that you can continue to keep track of how far deep you are in the tree.
This function then draws the sequence of nonbreaking spaces, just as drawLeaf does. Then it creates a special anchor element that has a NAME attribute as well as a HREF attribute. The NAME attribute consists of a string, "line," followed by the index of the branch object in the array.
The HREF attribute consists of another function, javascript:parent.outlineData.clickOnBranch(), which takes the branch object's index as its argument. The link displayed to the user consists of either [+] indicating that the branch can be opened or [-] indicating that the branch can be closed or collapsed.
Following the anchor that handles closing or opening the branch, the function also draws the link itself, if any. If there is no URL for the branch, it simply draws the title. Otherwise, it sets up a call to setContents, as in the drawLeaf function.
Finally, it calls drawLineBreak and increments parent.toc.level.
The clickOnBranch function, which the drawBranch function sets up to be the onClick event handler for the [+] or [-] links, is fairly simple:
function clickOnBranch(index) { nodeList[ index ].flags = nodeList[ index ].flags ^ __open__; var command = "parent.toc.location = 'treeview.htm#line" + index + "'"; setTimeout(command, 1ØØ); }
First, the exclusive-or operator, ^, toggles the branch object's open state. Then, the code creates a command to set the toc frame's location to treeview.htm at the branch's point. This ensures that when the outline is redrawn, the user will see the branch that was just clicked on.
Finally, the command is executed 100 milliseconds later, by using the window's setTimeout method. The command is executed with a delay because, under Netscape 2.01, executing it immediately causes a "recursive interrupt" error.
There are two other functions defined in outline.htm's SCRIPT element:
function nodeCount() { return nodeList.length; } and function writeNode(k) { nodeList[ k ].drawNode(); }
The purpose of these functions is to export some of the node functionality to code that doesn't know anything about nodes. The drawTree function needs to know how many nodes there are but it doesn't know about the nodeList object so it doesn't know that nodeList has a length property. Similarly, it doesn't know about nodes and it certainly doesn't know that they have a drawNode method. But the code in data.htm knows these things and can export the necessary knowledge to the outside world.
You've seen all the code in the data.htm file's SCRIPT element. There is one additional piece of JavaScript code in data.htm: the BODY element contains an ONLOAD handler:
ONLOAD="parent.toc.location = 'treeview.htm'"
This forces the loading of treeview.htm into the toc frame after the data is loaded.
Listing 5.2 shows the entire data.htm file.
Listing 5.2: The data.htm file
<HTML> <HEAD> <SCRIPT> <!-- var __leaf__ = 1; var __branch__ = 2; var __open__ = 4; var __empty__ = 8; function depthOpen(newValue) { this.saved = this.value; this.value = newValue; } function depthClose() { this.value = this.saved; } function drawEmptyNode() { depthList[ parent.toc.level ].close(); parent.toc.level--; } var gt = unescape("%3E"); function drawLineBreak() { parent.toc.document.write("<BR" + gt); } function setContents(url) { parent.contents.location = url; } function drawLeaf() { for (var j = Ø; j < parent.toc.level; j++) { if (depthList[ j + 1 ].value == Ø) { return; } } for (var k = Ø; k < parent.toc.level; k++) { parent.toc.document.write("    "); } parent.toc.document.write( "<A HREF=\"javaScript:parent.outlineData.setContents('" + this.url + "');\"" + gt + this.title + "</A" + gt); drawLineBreak(); } function clickOnBranch(index) { confirm("toggled"); nodeList[ index ].flags = nodeList[ index ].flags ^ __open__; var command = "parent.toc.location = 'treeview.htm#line" + index + "'"; setTimeout(command, 1ØØ); } function drawBranch() { for (var j = Ø; j < parent.toc.level; j++) { if (depthList[ j + 1 ].value == Ø) { parent.toc.level++; return; } } for (var k = Ø; k < parent.toc.level; k++) { parent.toc.document.write("    "); } parent.toc.document.write("<A NAME=\"line" + this.index + "\" HREF=\"javascript:parent.outlineData.clickOnBranch(" + this.index + ")\"" + gt); if ((this.flags & __open__) == Ø) { parent.toc.document.write("[+]"); depthList[ parent.toc.level + 1 ].open(Ø); } else { parent.toc.document.write("[-]"); depthList[ parent.toc.level + 1 ].open(1); } parent.toc.document.write("</A" + gt + " "); if (this.url.length == Ø) { parent.toc.document.write(this.title); } else { parent.toc.document.write( "<A HREF=\"javascript:parent.outlineData.setContents('" + this.url + "')\"" + gt + this.title + "</A" + gt); } drawLineBreak(); parent.toc.level++; } function node(flags, title, url) { this.index = nodeCtr; this.flags = flags; if (flags == __leaf__ || ((flags & __branch__) == __branch__)) { this.title = title; if (flags == __leaf__) { this.url = url; this.drawNode = drawLeaf; } else { if (node.arguments.length >= 3) { this.url = url; } else { this.url = ""; } this.drawNode = drawBranch; } } else { this.drawNode = drawEmptyNode; } } function depth() { this.saved = Ø; this.value = Ø; this.open = depthOpen; this.close = depthClose; } function MakeArray(n) { this.length = n; for (var i = 1; i <= n; i++) { this[ i ] = Ø; } return this; } var depthList = new MakeArray(3); for (var n = 1; n <= 3; n++) { depthList[ n ] = new depth(); } var nodeList = new MakeArray(53); var nodeCtr = Ø; nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__ + __open__, "Roadmap"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Home Page", "index.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Amusements", "amusement.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Comics", "amusement.html#Comics"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Funnies", "amusement.html#Funnies"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Games", "amusement.html#Games"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Media", "amusement.html#Media"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Travel", "amusement.html#Travel"); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Business", "business.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Book Stores", "business.html#Book Stores"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Other", "business.html#Other"); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Internet", "internet.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Providers", "internet.html#Providers"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Resources", "internet.html#Resources"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Surfing", "internet.html#Surfing"); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Web Pages", "internet.html#Web Pages"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Graphics", "internet.html#Graphics"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "HTML", "internet.html#HTML"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Icons", "internet.html#Icons"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Pages", "internet.html#Pages"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Tools", "internet.html#Tools"); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Reference Materials", "reference.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Books", "reference.html#Books"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Documents", "reference.html#Documents"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Education", "reference.html#Education"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Government", "reference.html#Government"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Jobs", "reference.html#Jobs"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Reference Servers", "reference.html#Reference Servers"); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Science Fiction", "sf.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Books", "sf.html#Books"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Miscellaneous Television", "sf.html#Miscellaneous Television"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Star Trek", "sf.html#Star Trek"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "World Designs", "sf.html#World Designs"); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Software", "software.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Academia", "software.html#Academia"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Analysis and Design", "software.html#AandD"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Linux", "software.html#Linux"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Programming", "software.html#Programming"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Shareware", "software.html#Shareware"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Vendors", "software.html#Vendors"); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__branch__, "Weather", "weather.html"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Images", "weather.html#Images"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "NWS Bulletins", "weather.html#NWS Bulletins"); nodeCtr++; nodeList[ nodeCtr ] = new node(__leaf__, "Weather Servers", "weather.html#Weather Servers"); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); nodeCtr++; nodeList[ nodeCtr ] = new node(__empty__); if (nodeCtr != nodeList.length) { alert("Node counter (" + nodeCtr + ") does not match nodeList.length (" + nodeList.length + ")"); } function nodeCount() { return nodeList.length; } function writeNode(k) { nodeList[ k ].drawNode(); } //--> </SCRIPT> </HEAD> <BODY ONLOAD="parent.toc.location = 'treeview.htm'"> </BODY> </HTML>
The treeview.htm file is fairly simple. It provides a place to draw the tree and it holds the code for drawing the entire tree. Because of how the nodes were designed, the code for drawing the tree may never need to change.
<HTML> <HEAD> <BASE HREF="http://www.he.net/~marcj/"> <SCRIPT LANGUAGE="JavaScript"> <!-- var level = Ø; function drawTree() { var depth = parent.outlineData.nodeCount(); for (var k = 1; k <= depth; k++) { parent.outlineData.writeNode(k); if (level < Ø) { alert("Too many empty nodes, index = " + k); level = Ø; break; } } if (level != Ø) { alert("Too few empty nodes, level = " + level); } } drawTree(); //--> </SCRIPT> </HEAD> <BODY> </BODY> </HTML>
The global variable, level, is initialized to 0. Then the drawTree function is defined.
The code first finds out how deep the tree is-how many nodes are in the tree. It uses the nodeCount function you saw in the data.htm file. This code has no knowledge of the nodeList object so it cannot access its length attribute directly.
Then, for each node, you call writeNode. Again, because the code in treeview.htm has no knowledge of node objects, it cannot call the drawNode method directly. After each call to writeNode, you make sure the tree is balanced-that there aren't more empty nodes than branch nodes. If the level drops below zero, you've encountered one more empty node than there are branch nodes. You issue an alert in this case, indicating the node where you found the problem.
After all of the nodes are drawn, level should be zero again. If it isn't, there weren't enough empty nodes in the list of nodes. Again, you display an alert window indicating the problem and how many empty nodes are missing.
After the function is defined, you call it. At this point, you're still loading the HEAD element, so everything you're writing to the document object will be displayed. There is no conflict with what you've written so far, because you haven't written anything yet.
Figures 5.6 and 5.7 show typical pictures of your new document. Figure 5.6 shows the document when first loaded. Figure 5.7 shows the document after a previously closed branch has been opened and one of the leaves displayed.
Figure 5.6 : JavaScript outline when first loaded.
Figure 5.7 : JavaScript outline with a document displayed and part of the outline expanded.
There is no program that cannot, in some way, be improved upon. So it is with the JavaScript outline presented in this chapter. There are some inefficiencies in the code, although they pertain mostly to the file length of data.htm. You could encapsulate calls to parent.toc.document.write in a single function, for example. This really won't help much, as the length of time it takes to load data.htm is really a function of how long and complex your outline is.
You could modify the node constructor function to keep track of the depth of the nodes. This would make it easy to further automate the construction of the depthList array.
You can add additional HTML to be written before and after the document outline, adding information about the document, your corporate logo, a copyright notice, and so forth. You can do this by either adding code directly to the drawTree function or by creating functions (perhaps drawHead and drawFoot) in the data.htm file that can be called from drawTree.
It's also fairly easy to replace [+] and [-] with images, such as open and closed file folders (don't forget to specify the HEIGHT and WIDTH attributes!) or with more descriptive text. Just don't forget to keep the images or text small. You don't want to crowd the frame!
Modifying this example to suit your own needs is simply a matter of replacing the nodeList and depthList initialization sequences with sequences of your own liking. Change the BASE element in treeview.htm's to point to your own pages' location.