Chapter 5
An Outlined Document
CONTENTS
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.
The scenario
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.
The
requirements
There are five requirements for putting together your outline.
- The outline must be able to accommodate a document that is divided
into several Web pages.
- The user must be able to access the document outline easily from any
point on any of the constituent Web pages.
- The document outline must help the user navigate the constituent Web
pages.
- The user must be able to quickly navigate the document outline, regardless
of its length.
- The design of the document outline should be reusable. In particular,
it must not set arbitrary limits, such as the maximum number of links in
the outline or how deeply nested the outline can be, that might hinder
the reuse of the design. The design of the outline must be extensible to
handle arbitrarily complex documents.
The
solution (without JavaScript)
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.
The
solution (with JavaScript)
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.
Frame in the Outline
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.
Make the Document Outline
Smarter
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.
Creating a collapsible tree structure
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.
How deep is the tree? And where are we, anyway?
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;
}
Creating arrays of depth and node objects
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.
Drawing the nodes
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.
Exporting knowledge of nodes
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.
Loading the outline
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>
Drawing the tree
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
the example for your own use
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.