Полезная информация

Chapter 8

Frames, Documents, and Windows


CONTENTS


Now that you have learned the basics of JavaScript and how to work with forms, you are ready to look at another advanced feature of JavaScript: frames.

Frames provide the ability to divide a document window into distinct sections, each of which contains different HTML files that can also be manipulated using JavaScript.

Besides the capability to manipulate frames, JavaScript also provides the document object, which provides properties and methods for dealing with anchors, links and colors, and the window object-the top level object of a web document window.

In this chapter we cover these topics:

An Introduction to Frames

Frames are one of the most widely used new features of Navigator 2.0 and 3.0.

By using a few simple extensions to the HTML standard, Web authors are able to achieve sophisticated control over the layout of information in the Web browser window by dividing the window into rectangular sections and loading separate HTML files into each section of the window.

In addition, links in one frame can update another frame, and the result of processing form data in a CGI script on a server can be targeted at another frame.

Even without the addition of JavaScript, frames have enabled the addition of a type of interactivity that wasn't possible before, using regular HTML. For instance, sites now feature fixed tool bars and permanent search forms such as the one in Figure 8.1.

Figure 8.1 : Using frames, The Dataphile On-line in Hong Kong has permanent search forms at its site.

The FRAMESET Tag

A page is divided into frames using the FRAMESET tag. The tag is used in the top-level document defining a window containing frames and is used to specify how to divide the document window.

Because windows divided into frames are created from multiple HTML files, it is important to keep the hierarchical relationship of documents in mind. In a document window divided into frames, the top-level document is the HTML document that defines the frames and files that will load into those frames.

The FRAMESET container tag takes several attributes. The two basic ones are ROWS and COLS. A FRAMESET tag takes either one of these or both to divide a document into a set of rows or columns. For instance,

<FRAMESET COLS="25,*,25">

would define three columns. The two outer columns would each be 25 pixels wide, and the middle column would take the remaining space depending on the size of the window. In this example the asterisk (*) represents the remaining available space after the space is allocated for the other frames.

In addition to specifying the size of frames in pixels, the size of columns and rows can be defined using percentages relative to the space available to the document:

<FRAMESET ROWS="35%,*">

Tip
The use of percentages to define the size of frames is useful when you consider that different users will have different size monitors running at different resolutions. If you normally use a very high resolution, you may feel it is okay to define the width of a column as 700 pixels, but to a user running at standard 640¥480 VGA resolution, this frame would be wider than his display allows.

The preceding FRAMESET tag would divide the display into two rows. The top row would be 35 percent of the height of the display area, and the bottom row would fill the remaining space (using the asterisk again).

Note
The FRAMESET tag replaces the BODY tag in a file. Files with FRAMESET containers are not used to directly display HTML data in Navigator.

The FRAME Tag

Inside a FRAMESET container, the FRAME tag is used to specify which files should be displayed in each frame. The URLs of the files-which can be relative or absolute-should be specified using the SRC attribute in the same way as the IMG tag is used to include images in an HTML document.

The terms relative and absolute refer to two different ways of indicating the location of files in HTML. In absolute URLs, the complete protocol (the part before the colon), domain name, and path of a file are provided. For instance,

http://www.juxta.com/juxta/docs/prod.htm

is an absolute URL.

In relative URLs, the protocol, domain name, and complete path are not indicated. Instead, the location of the file relative to the current file is indicated. If the file indicated the URL is in the same directory, then just the filename is needed. If the file is in a subdirectory, then the path from the current directory is needed.

For example, the following creates a document with two rows.

<FRAMESET ROWS="35%,*">
  <FRAME SRC="menu.html">
  <FRAME SRC="welcome.html">
</FRAMESET>

The top is 35 percent of the available space, and the bottom takes up the remaining 65 percent. The file menu.html is loaded into the top frame, and the file welcome.html is displayed in the lower frame.

In addition to the SRC attribute, the FRAME tag can take several other attributes as outlined in Table 8.1.

Table 8.1. Attributes for the FRAME tag.

AttributeDescription
SRC Specifies the URL of the HTML file to be displayed in the frame.
NAME Specifies the name of the frame so that it can be referenced by HTML tags and JavaScript scripts.
NORESIZE Specifies that the size of a frame is fixed and cannot be changed by the user.
SCROLLING Specifies whether scroll bars are available to the user. This can take a value of YES, NO, or AUTO.
MARGINHEIGHT Specifies the vertical offset in pixels from the border of the frame.
MARGINWIDTH Specifies the horizontal offset in pixels from the border of the frame.

To illustrate these attributes, look at the earlier example. The user can resize the frames by dragging on the border between the frames. By adding NORESIZE to either of the frames, this is prevented:

<FRAMESET ROWS="35%,*">
  <FRAME SRC="menu.html" NORESIZE>
  <FRAME SRC="welcome.html">
</FRAMESET>

or

<FRAMESET ROWS="35%,*">
  <FRAME SRC="menu.html">
  <FRAME SRC="welcome.html" NORESIZE>
</FRAMESET>

Typically, if a document fills more space than the frame it is assigned to, Navigator will add scroll bars to the frame. If you don't want scroll bars to appear, regardless of the size of the frame, you can use SCROLLING=NO to prevent them from being used:

<FRAMESET ROWS="35%,*">
  <FRAME SRC="menu.html">
  <FRAME SRC="welcome.html" SCROLLING=NO>
</FRAMESET>

As you can see in Figure 8.2, by using SCROLLING=NO, no scroll bars appear in the lower frame, even though the graphic is larger than the frame.

Figure 8.2 : Preventing scroll bars in a frame, even when the document is larger than the frame.

Nesting Frames

Looking at examples of frames on the Web, it quickly becomes obvious that many sites have more complex layouts than simply dividing the window into rows or columns. For instance, in Figure 8.1 you saw an example of a site that has rows and columns combined to produce a very complex layout.

This is achieved by nesting, or embedding, FRAMESET containers within each other. For instance, if you want to produce a document with three frames where you have two rows and the bottom row is further divided in two columns (to produce three frames), you could use a structure like this:

<FRAMESET ROWS="30%,*">
  <FRAME SRC="menu.html">
  <FRAMESET COLS="50%,50%">
    <FRAME SRC="welcome.html">
    <FRAME SRC="pic.html" SCROLLING=AUTO>
  </FRAMESET>
</FRAMESET>

A similar result can be achieved by using separate files. For instance, if the first file contains

<FRAMESET ROWS="30%,*">
  <FRAME SRC="menu.html">
  <FRAME SRC="bottom.html">
</FRAMESET>

and the file bottom.html contains

<FRAMESET COLS="50%,50%">
  <FRAME SRC="welcome.html">
  <FRAME SRC="pic.html" SCROLLING=AUTO>
</FRAMESET>

then you would get the same result as the previous example where both FRAMESET containers appeared in the same file.

To get a better idea of how this works, you can look at the source code for The Dataphile On-line which you saw in Figure 8.1. The following source code in Listing 8.1 combines the nested framesets from multiple files into a single file:


Listing 8.1. The source code for The Dataphile On-line frames.
<FRAMESET ROWS="100,*">
  <FRAMESET COLS="500,*">
    <FRAME SRC="banner.htm" NORESIZE MARGINHEIGHT=0
MARGINWIDTH=0 SCROLLING="no">
    <FRAMESET ROWS="30,*">
      <FRAME SRC="constant.htm" NORESIZE MARGINHEIGHT=0
MARGINWIDTH=0 SCROLLING="no">
      <FRAME SRC="menu.htm" NORESIZE MARGINHEIGHT=0
MARGINWIDTH=0 SCROLLING="auto">
     </FRAMESET>
  </FRAMESET>
  <FRAMESET COLS="*,250">
    <FRAMESET ROWS="*,50">
      <FRAME SRC="welcome.htm" NAME="middle"
SCROLLING="auto">
      <FRAME SRC="search.htm" MARGINHEIGHT=2
MARGINWIDTH=2 SCROLLING="auto">
    </FRAMESET>
    <FRAMESET ROWS="50,*">

      <FRAME SRC="newshead.htm" SCROLLING="no"
MARGINHEIGHT=0 MARGINWIDTH=0>
      <FRAME SRC="newstory.htm" SCROLLING="auto"
MARGINGHEIGHT=2 MARGINWIDTH=2>
    </FRAMESET>
  </FRAMESET>
</FRAMESET>

You start by dividing the window into two rows. The top row is divided into two columns, and the right column is further divided into two rows. Likewise, the bottom row is divided into two columns. The left column is divided into two rows, as is the right column.

The NOFRAMES Tag

You may have noticed that the one problem with files containing FRAMESET containers is that they will go undisplayed on a non-Netscape browser because other browsers don't support this extension to HTML.

This is addressed by the NOFRAMES container tag. Any HTML code contained between the NOFRAMES tags is ignored by the Navigator browser but will be displayed by any other browser.

For instance, this code

<HTML>

<HEAD>
<TITLE>NOFRAMES Example</TITLE>
</HEAD>

<FRAMESET ATTRIBUTES>
   <FRAME SRC="filename">
   <FRAME SRC="filename">
</FRAMESET>

<NOFRAMES>
   HTML code for other browsers
</NOFRAMES>

</HTML>

could be used to produce output like Figure 8.3 in Navigator 2.0 or 3.0 but like Figure 8.4 in another browser.

Figure 8.3 : Only Navigator 2.0 recognizes the FRAMESET tag.

Figure 8.4 : The NOFRAMES tag provides an alternative page for users of other browsers.

Naming Frames

In order to place (or target) the result of links or form submissions in specific frames, you can name frames using the NAME attribute of the FRAME tag. For instance,

<FRAMESET COLS="50%,*">
  <FRAME SRC="menu.html" NAME="menu">
  <FRAME SRC="welcome.html" NAME="main">
</FRAMESET>

would create two named frames called menu and main. In the file menu.html, you could have hypertext references target the main frame using the TARGET attribute:

<A HREF="choice1.html" TARGET="main">

Likewise, the result of a form submission could be targeted the same way:

<FORM METHOD=POST ACTION="/cgi-bin/test.pl" TARGET="main">

The TARGET attribute can also be used in the BASE tag to set a global target for all links in a document. For instance, if an HTML document has this BASE tag in its header

<BASE TARGET="main">

then all hypertext and results of form processing will appear in the FRAME named "main". This global targeting is overridden by using a TARGET attribute in an A tag or FORM tag in the body of the HTML document.

Note
Naming and targeting are not relevant to frames only. Windows can also be named and targeted as you learn later in this chapter, in the section about the window object.

In addition to targeting named frames, there are several special terms which can be used in the TARGET attributes. These are outlined in Table 8.2.

Table 8.2. Special values for the TARGET attribute.

ValueDescription
_blank Causes a link to load in a new, unnamed window.
_self Causes a link to load in the same window the anchor was clicked in. (This can be used to override a target specified in a BASE tag.)
_parent Causes a link to load in the immediate FRAMESET parent.
_top Causes a link to load in the full body of the window regardless of the number of nested FRAMESET tags.

Working with Frames in JavaScript

JavaScript provides the frames property of the window object for working with different frames from a script.

The frames property is an array of objects with an entry for each child frame in a parent frameset. The number of frames is provided by the length property.

For instance, in a given window or frameset with two frames, you could reference the frames as parent.frames[0] and parent.frames[1]. The index of the last frame could be parent.frames.length.

By using the frames array, you can access the functions and variables in another frame, as well as objects, such as forms and links, contained in another frame. This is useful when building an application that spans multiple frames but that also must be able to communicate between the frames.

Note
Each frame has a different document, location, and history object associated with it. This is because each frame contains a separate HTML document and has a separate history list. You will learn about the document object later in this chapter and about the history object in Chapter 10, "Strings, Math, and the History List."

For example, if you have two frames, you could create a form in the first frame to provide the user with a field to enter an expression. Then you could display the results in a form in the other frame.

This cross-frame communication is achieved by referencing the document object's forms[] array in the second frame with parent.frames[1].document.forms[0]. In Listing 8.2 you build a simple calculator to evaluate expressions entered by users and use frames to display the output.


Listing 8.2. Cross-frame communication.
<!-- HTML CODE FOR PARENT FRAMESET (this is a separate file) -->

<HTML>
<HEAD>
<TITLE>Listing 8.2</TITLE>
</HEAD>

<FRAMESET COLS="50%,*">
   <FRAME SRC="input.html">
   <FRAME SRC="output.html">
</FRAMESET>

</HTML>
<!-- HTML FOR INPUT FRAME (this is a separate file called input.html-->
<HTML>

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
function update(field) {
   var result = field.value;
   var output = "" + result + " = " + eval(result);

   parent.frames[1].document.forms[0].result.value = output;
}
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</HEAD>

<BODY>
<FORM METHOD=POST>
<INPUT TYPE=text NAME="input" onChange="update(this);">
</FORM>
</BODY>

</HTML>
<HTML>

<!-- HTML FOR OUTPUT FRAME (this is a separate file called output.html)-->

<BODY>
<FORM METHOD=POST>
<TEXTAREA NAME=result ROWS=2 COLS=20
WRAP=SOFT></TEXTAREA>

</FORM>
</BODY>

</HTML>

In this example, it is important to note two things in the update() function. First, the eval() function used to evaluate the expression provided by the user doesn't work properly on the Windows 3.11 version of Navigator 2.0. Second, when you evaluate the expression and store the result in the variable output

var output = "" + result + " = " + eval(result);

you start the expression with "" to ensure a string value is assigned to the variable output.

In addition to specifying frames using the frames array, if you name the frames, you can specify certain frames using the form parent.framename. In the example you just saw, if you name the frames input and output, you could rewrite the update() function:

function update(field) {
   var result = field.value;
   var output = "" + result + " = " + eval(result);
   parent.output.form[0].result.value = output;
}

The frameset in this example would look like

<FRAMESET COLS="50%,*">
   <FRAME SRC="input.html" NAME="input">
   <FRAME SRC="output.html" NAME="output">
</FRAMESET>

The naming of elements can be taken one step further, and the forms can be named. For instance, if you name the forms inputForm and outputForm, then the files input.html and output.html can look like this:

<!-- HTML FOR INPUT FRAME (this is a separate file called input.html-->
<HTML>

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
function update(field) {
   var result = field.value;
   var output = "" + result + " = " + eval(result);

   parent.output.document.outputForm.result.value = output;
}
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</HEAD>

<BODY>
<FORM METHOD=POST NAME="inputForm">
<INPUT TYPE=text NAME="input" onChange="update(this);">
</FORM>
</BODY>

</HTML>
<HTML>

<!-- HTML FOR OUTPUT FRAME (this is a separate file called output.html)-->

<BODY>
<FORM METHOD=POST NAME="outputForm">
TEXTAREA NAME=result ROWS=2 COLS=20
WRAP=SOFT></TEXTAREA>
</FORM>
</BODY>

</HTML>

Notice, then, how the output field can be referred to with

parent.output.document.-outputForm.result

Nested Frames in JavaScript

With nested frames, cross-frame communication gets a little bit more complicated.

When building nested framesets, you can use subdocuments for each frameset. When you do this, the parent will only refer back to the document containing the parent frameset and not the top-level frameset.

For example, referring to the previous expression evaluation example, if you want to divide the display into four equal quarters (as shown in Figure 8.5) and then use only two of them, you would have to change the FRAMESET to be something like this:

Figure 8.5 : Using nested framesets produces complex screen Layouts.

<FRAMESET ROWS="50%,*">
   <FRAME SRC="top.html">
   <FRAME SRC="bottom.html">
</FRAMESET>

Where top.html and bottom.html contain further nested framesets:

<!-- HTML FOR top.html -->

<FRAMESET COLS="50%,*">
      <FRAME SRC="input.html" NAME="input">
      <FRAME SRC="logo.html">
   </FRAMESET>
<!-- HTML FOR bottom.html -->

   <FRAMESET COLS="50%,*">
      <FRAME SRC="about.html">
      <FRAME SRC="output.html" NAME="output">
   </FRAMESET>

If input.html and output.html are still the files where the work is being done (logo.html and about.html are cosmetic), then you can't use the update() function you were using, because parent.frame[1] in the script will be referring to the frame containing logo.html-the parent of the input frame is the first nested frameset. You want to reference the frame containing output.html, which is in the second nested frameset. To reference this document, you need to go up two parent levels and then down two frames to reach output.html:

parent.parent.frame[1].frame[1]

With the named frame, this would become parent.parent.frame[1].output.

In addition to referring to variables and objects in other frames, the same technique can be used to invoke functions in other frames. For instance, you could add a function to output.html to handle displaying the results in the appropriate text field. Then, in input.html you could simply call the function and pass it the value of the variable output:

<!-- HTML FOR INPUT FRAME (this is a separate file called input.html-->
<HTML>

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
function update(field) {
   var result = field.value;
   var output = "" + result + " = " + eval(result);

   parent.output.displayResult(output);
}
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</HEAD>

<BODY>
<FORM METHOD=POST NAME="inputForm">
<INPUT TYPE=text NAME="input" onChange="update(this);">
</FORM>
</BODY>

</HTML>
<HTML>

<!-- HTML FOR OUTPUT FRAME (this is a separate file called output.html)-->

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

function displayResult(output) {

   document.ouputForm.result.value = output;

}
// STOP HIDING -->
</SCRIPT>
</HEAD>

<BODY>
<FORM METHOD=POST NAME="outputForm">
<TEXTAREA NAME=result ROWS=2 COLS=20
WRAP=SOFT></TEXTAREA>
</FORM>
</BODY>

</HTML>

Bill Dortch's hIdaho Frameset.
It quickly becomes obvious that any program can get tangled up in deeply nested frames, all of which must interact with each other to produce an interactive application.
This can quickly lead to confusing references to
parent.parent.frameA.frameB.frameC.form1.fieldA.value
or
parent.frameD.frameE.functionA()
To make this easier, Bill Dortch has produced the hIdaho Frameset. This is a set of freely available JavaScript functions to make dealing with functions in nested framesets easier. Dortch has made the hIdaho Frameset available for others to use in their scripts. Full information about the Frameset is on-line at
http://www.hidaho.com/frameset/
Using this Frameset, it is possible to register functions in a table and then call them from anywhere in a nested frameset without needing to know which frames they are defined in and without needing to use a long, and often confusing, sequence of objects and properties to refer to them. In addition, frames and framesets can be easily moved without having to recode each call to the affected functions across all your documents.
The hIdaho Frameset also provides a means of managing the timing of functions so you can ensure that a function has been loaded and registered before attempting to call it. This is especially useful during window and frame refreshes, when documents are reevaluated.
The source code is reproduced on the CD-ROM:
<script language="JavaScript">
<!-- begin script
//****************************************************************
// The hIdaho Frameset. Copyright (C) 1996 Bill Dortch, hIdaho Design
// Permission is granted to use and modify the hIdaho Frameset code,
// provided this notice is retained.
//****************************************************************
var debug = false;
var amTopFrameset = false;
// set this to true for the topmost frameset
var thisFrame = (amTopFrameset) ? null : self.name;
var maxFuncs = 32;
function makeArray (size) {
    this.length = size;
    for (var i = 1; i <= size; i++)
         this[i] = null;
    return this;
}
var funcs = new makeArray ((amTopFrameset) ? maxFuncs : 0);
function makeFunc (frame, func) {
    this.frame = frame;
    this.func = func;
    return this;
}
function addFunction (frame, func) {
    for (var i = 1; i <= funcs.length; i++)
         if (funcs[i] == null) {
             funcs[i] = new makeFunc (frame, func);
             return true;
         }
    return false;
}
function findFunction (func) {
    for (var i = 1; i <= funcs.length; i++)
         if (funcs[i] != null)
             if (funcs[i].func == func)
                  return funcs[i];
    return null;
}
function Register (frame, func) {
    if (debug) alert (thisFrame + ":
Register(" + frame + "," + func + ")");
    if (Register.arguments.length < 2)
         return false;
    if (!amTopFrameset)
         return parent.Register (thisFrame + "." + frame, func);
    if (findFunction (func) != null)
         return false;
    return addFunction (frame, func);
}
function UnRegister (func) {
    if (debug) alert (thisFrame + ": UnRegister(" + func + ")");
    if (UnRegister.arguments.length == 0)
         return false;
    if (!amTopFrameset)
         return parent.UnRegister (func);
    for (var i = 1; i <= funcs.length; i++)
         if (funcs[i] != null)
             if (funcs[i].func == func) {
                  funcs[i] = null;
                  return true;
             }
    return false;
}
function UnRegisterFrame (frame) {
    if (debug) alert (thisFrame + ": UnRegisterFrame(" + frame + ")");
    if (UnRegisterFrame.arguments.length == 0)
         return false;
    if (!amTopFrameset)
         return parent.UnRegisterFrame (thisFrame + "." + frame);
    for (var i = 1; i <= funcs.length; i++)
         if (funcs[i] != null)
             if (funcs[i].frame == frame) {
                  funcs[i] = null;
             }
    return true;
}
function IsRegistered (func) {
    if (debug) alert (thisFrame + ": IsRegistered(" + func + ")");
    if (IsRegistered.arguments.length == 0)
         return false;
    if (!amTopFrameset)
         return parent.IsRegistered (func);
    if (findFunction (func) == null)
         return false;
    return true;
}
function Exec (func) {
    if (debug) alert (thisFrame + ": Exec(" + func + ")");
    var argv = Exec.arguments;
    if (argv.length == 0)
         return null;
    var arglist = new makeArray(argv.length);
    for (var i = 0; i < argv.length; i++)
         arglist[i+1] = argv[i];
    var argstr = "";
    for (i = ((amTopFrameset) ? 2 : 1); i <= argv.length; i++)
         argstr += "arglist[" + i + "]" + ((i < argv.length) ? "," : "");
    if (!amTopFrameset)
         return eval ("parent.Exec(" + argstr + ")");
    var funcobj = findFunction (func);
    if (funcobj == null)
         return null;
    return eval ("self." + ((funcobj.frame == null) ? "" :
(funcobj.frame + "."))+ funcobj.func + "(" + argstr + ")");
}
//****************************************************************
// End of hIdaho Frameset code.
//****************************************************************
// end script -->
</script>
The source code should be included in each frameset document in your hierarchy of nested framesets. The only important distinction is that the amTopFrameset variable should be set to false for all framesets except the top.
Each of the functions is used for a different purpose.
The Register() function.
The Register() function is used to register functions in the function table. It is called from the function's frame by referring to the function in the immediate parent frameset as follows: parent.Register(self.name,"functionName").
self refers to the currently opened frame or window. You learn more about it later in this chapter.
The function will return true if there is room in the function table and the name is not currently registered. Otherwise, it will return false.
The UnRegister() function.
This function does exactly what its name suggests: It removes a specific function from the registration table. It takes a single argument: UnRegister("functionName").
The UnRegisterFrame() function.
This function unregisters all functions registered for a specified frame. It takes the frame name as a single argument.
The IsRegistered() function.
A call to IsRegistered("frameName") returns true if the function is registered and false if it isn't.
The Exec() function.
The Exec() function is used to call a specific function. It takes at least one argument-the name of the function-but can take more in the form of parameters to pass to the called function as arguments. For instance, if you want to call the function functionA and pass two arguments, arg1 and arg2, you could call
parent.Exec("functionA",arg1,arg2);
The Exec() function returns the value returned by the specified function.
It is not considered harmful to call an unregistered function using Exec(). If you do, a null value is returned. This can cause confusion, of course, if a legitimate value returned by the specified function could be the null value. This can happen when the frame containing the desired function has not finished loading when another frame's script tries to call it.
One way that Dortch suggests dealing with this timing problem is to use the IsRegsistered() function to ensure a function exists before calling it:
function intialize() {
      if (!parent.IsRegistered("functionA")) {
             setTimeout("initialize()",250);
             return;
      }
      JavaScript code
      parent.Exec("functionA",arg1,arg2);
      JavaScript code
}
In this example, the function initialize() will not get past the first if statement unless the function functionA has been registered. The function uses the setTimeout() method to cause a pause for 250 milliseconds after which
initialize() is to be called again.
setTimeout() is a method of the window object that enables a pause to be specified before executing a command or evaluating an expression. We will look at the setTimeout() method, and the related clearTimeout() method, later in this chapter when we cover the window object.
The initialize() function is a recursive function that will continue to call itself every quarter second until the desired function is registered.
As indicated on the hIdaho Frameset Web page, Dortch has purposely not written functions to provide access to variables and other objects and properties in other frames because he feels that well-designed, multi-frame applications should use function calls to access information in other frames. Look for an updated version coming soon to his Web page.

Putting Nested Framesets to Work

Now that you know how to work with frames, you are going to produce a testing tool that teachers can use to easily produce a test in any given subject.

To do this, you will use nested framesets. The top-level frameset will produce three rows: one for the title, one for the work area, and one for a level selector.

The middle row, the work area, will be split into two equal columns. The left side will contain a form for the student to enter his answer as well as a field to display the current score. The right column will be used to display the questions and the result of a student's answer.

For these purposes, you only need to look at the source code for the student entry form and the level selection tool in the bottom frame. You will use Bill Dortch's hIdaho Frameset to make working with the nested framesets easier.

The frameset is defined by two files: the top-level test.htm (Listing 8.3) and work.htm (Listing 8.4) which defines the workspace in the middle row. work.htm contains the nested frameset referred to in test.htm (<FRAME SRC="work.htm" NAME="work">):


Listing 8.3. Top-level frameset (test.htm).
<!-- FRAMESET FROM test.htm -->
<FRAMESET ROWS="20%,*,20%">
    <FRAME SRC="title.htm">
    <FRAME SRC="work.htm" NAME="work">
    <FRAME SRC="level.htm" NAME="level">
</FRAMESET>


Listing 8.4. The nested frameset (work.htm).
<!-- FRAMESET FROM work.htm -->
<FRAMESET COLS="50%,*">
   <FRAME SRC="form.htm" NAME="form">
   <FRAME SRC="output.htm" NAME="output">
</FRAMESET>

Both test.htm and work.htm would include the source code of the hIdaho Frameset so that the programs can easily call functions in other frames. In the file test.htm, amTopFrameset should be set to true with the statement amTopFrameset = true.

All of the functions and information are kept in the file form.htm (Listing 8.5). form.htm is one of the frames in the nested frameset.


Listing 8.5. The entry form (form.htm).
<!-- SOURCE CODE OF form.htm -->
<HTML>

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

var currentLevel=1;
var currentQuestion=1;
var toOutput = "";

// DEFINE LEVEL ONE
q1 = new question("1 + 3",4);
q2 = new question("4 + 5",9);
q3 = new question("5 - 4",1);
q4 = new question("7 + 3",10);
q5 = new question("4 + 4",8);
q6 = new question("3 - 3",0);
q7 = new question("9 - 5",4);
q8 = new question("8 + 1",9);
q9 = new question("5 - 3",2);
q10 = new question("8 - 3",5);
levelOne = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE LEVEL TWO
q1 = new question("15 + 23",38);
q2 = new question("65 - 32",33);
q3 = new question("99 + 45",134);
q4 = new question("34 - 57",-23);
q5 = new question("-34 - 57",-91);
q6 = new question("23 + 77",100);
q7 = new question("64 + 32",96);
q8 = new question("64 - 32",32);
q9 = new question("12 + 34",46);
q10 = new question("77 + 77",154);
levelTwo = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE LEVEL THREE
q1 = new question("10 * 7",70);
q2 = new question("15 / 3",5);
q3 = new question("34 * 3",102);
q4 = new question("33 / 2",16.5);
q5 = new question("100 / 4",25);
q6 = new question("99 / 6",16.5);
q7 = new question("32 * 3",96);
q8 = new question("48 / 4",12);
q9 = new question("31 * 0",0);
q10 = new question("45 / 1",45);
levelThree = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE TEST
test = new newTest(levelOne,levelTwo,levelThree);

function newTest(levelOne,levelTwo,levelThree) {
   this[1] = levelOne;
   this[2] = levelTwo;
   this[3] = levelThree;
}

function level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10) {
   this[1] = q1;
   this[2] = q2;
   this[3] = q3;
   this[4] = q4;
   this[5] = q5;
   this[6] = q6;
   this[7] = q7;
   this[8] = q8;
   this[9] = q9;
   this[10] = q10;
}

function question(question,answer) {
   this.question = question;
   this.answer = answer;
}

parent.Register(self.name,"startTest");
function startTest(newLevel) {
   currentLevel=newLevel;
   currentQuestion=1;
   document.forms[0].answer.value="";
   document.forms[0].score.value=0;
   displayQuestion();
}

function displayQuestion() {
   ask = test[currentLevel][currentQuestion].question;
   answer = test[currentLevel][currentQuestion].answer;
   toOutput = "" + currentQuestion + ". What is " + ask + "?";
   document.forms[0].answer.value = "";
   window.open("display.htm","output");
}

parent.Register(self.name,"output");
function output() {
   return toOutput;
}

function checkAnswer(form) {


   answer = form.answer.value;

   if (answer == "" || answer == null) {
      alert("Please enter an answer.");
       return;
   }

   correctAnswer = test[currentLevel][currentQuestion].answer;
   ask = test[currentLevel][currentQuestion].question;
   score = form.score.value;
   if (eval(answer) == correctAnswer) {
      toOutput = "Correct!";
      score ++;
      form.score.value = score;
   } else {
      toOutput = "Sorry! " + ask + " is " + correctAnswer + ".";
   }
   window.open("display.htm","output");
   if (currentQuestion < 10) {
      currentQuestion ++;
      setTimeout("displayQuestion()",3000);
   } else {
      toOutput = "You're Done!<BR>You're score is " + score + " out of 10.";
      setTimeout("window.open('display.htm','output')",3000);
      form.answer.value="";
      form.score.value="0";
   }
}

function welcome() {
   toOutput = "Welcome!";
   window.open("display.htm","output");
}

// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>

</HEAD>

<BODY BGCOLOR="#FFFFFF" TEXT="#0000FF" onLoad="welcome();">

<FORM METHOD=POST>
<CENTER>
<STRONG>Type You're Answer Here:</STRONG><BR>
<INPUT TYPE=text NAME=answer SIZE=30><P>
<INPUT TYPE=button NAME=done VALUE="Check Answer"
onClick="checkAnswer(this.form);"><P>
Correct Answers So Far:<BR>
<INPUT TYPE=text NAME=score VALUE="0" SIZE=10>
</FORM>

</BODY>

</HTML>

The file level.htm (Listing 8.6) provides users with three buttons to select different levels. level.htm is the bottom frame in the parent frameset:


Listing 8.6. Level selection controls.
<!-- SOURCE CODE OF level.htm -->
<HTML>

<BODY BGCOLOR="#000000" TEXT="#FFFFFF">
<CENTER>
<STRONG>
Select a level here:
<FORM METHOD=POST>
<INPUT TYPE=button NAME="one" VALUE="Level One"
onClick="parent.Exec('startTest',1);">
<INPUT TYPE=button NAME="two" VALUE="Level Two"
onClick="parent.Exec('startTest',2);">
<INPUT TYPE=button NAME="three" VALUE="Level Three"
onClick="parent.Exec('startTest',3);">
</FORM>
</STRONG>
</CENTER>
</BODY>

</HTML>

All display in the frame named output is done by reloading the file display.htm (Listing 8.7):


Listing 8.7. display.htm is reloaded to update the output.
<!-- SOURCE CODE OF display.htm -->
<HTML>

<BODY BGCOLOR="#0000FF" TEXT="#FFFFFF">
<H1>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

document.write(parent.Exec("output"));

// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</H1>
</BODY>

</HTML>

Finally, title.htm (Listing 8.8) contains the information displayed in the top frame of the parent frameset:


Listing 8.8. The title frame.
<!-- SOURCE CODE OF title.htm -->
<HTML>

<BODY BGCOLOR="#000000" TEXT="#00FFFF">
<CENTER>
<H1>
<STRONG>
The Math Test
</STRONG>
</H1>
</CENTER>
</BODY>

</HTML>

The final product would look something like Figure 8.6.

Figure 8.6 : Using nested framesets to produce a multilevel math test.

As you can see in the source code listings, the file form.htm (Listing 8.5) is the centerpiece of the entire application. It is in this file that all the work of checking answers, displaying questions and results, and resetting the test is done.

Let's look at the document section by section.

var currentLevel=1;
var currentQuestion=1;
var toOutput = "";

These are the key global variables in the script which are used to keep track of the current level being tested, the current question being tested, and what should next be displayed in the output frame.

Next the questions and answers for the three levels are defined:

// DEFINE LEVEL ONE
q1 = new question("1 + 3",4);
q2 = new question("4 + 5",9);
q3 = new question("5 - 4",1);
q4 = new question("7 + 3",10);
q5 = new question("4 + 4",8);
q6 = new question("3 - 3",0);
q7 = new question("9 - 5",4);
q8 = new question("8 + 1",9);
q9 = new question("5 - 3",2);
q10 = new question("8 - 3",5);
levelOne = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE LEVEL TWO
q1 = new question("15 + 23",38);
q2 = new question("65 - 32",33);
q3 = new question("99 + 45",134);
q4 = new question("34 - 57",-23);
q5 = new question("-34 - 57",-91);
q6 = new question("23 + 77",100);
q7 = new question("64 + 32",96);
q8 = new question("64 - 32",32);
q9 = new question("12 + 34",46);
q10 = new question("77 + 77",154);
levelTwo = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE LEVEL THREE
q1 = new question("10 * 7",70);
q2 = new question("15 / 3",5);
q3 = new question("34 * 3",102);
q4 = new question("33 / 2",16.5);
q5 = new question("100 / 4",25);
q6 = new question("99 / 6",16.5);
q7 = new question("32 * 3",96);
q8 = new question("48 / 4",12);
q9 = new question("31 * 0",0);
q10 = new question("45 / 1",45);
levelThree = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE TEST
test = new newTest(levelOne,levelTwo,levelThree);

function newTest(levelOne,levelTwo,levelThree) {
   this[1] = levelOne;
   this[2] = levelTwo;
   this[3] = levelThree;
}

function level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10) {
   this[1] = q1;
   this[2] = q2;
   this[3] = q3;
   this[4] = q4;
   this[5] = q5;
   this[6] = q6;
   this[7] = q7;
   this[8] = q8;
   this[9] = q9;
   this[10] = q10;
}
function question(question,answer) {
   this.question = question;
   this.answer = answer;
}

The test consists of three levels with 10 questions each. You store all this information in a series of objects. The question object has two properties: question and answer. The level object consists of 10 questions as properties. The test object has three properties-each level of the test.

Notice that you only name the properties in the question object. This is because you will want to access the level and particular question using numeric indexes rather than names. For instance, you could refer to level one as test[1] and question three of level one as test[1][3] (notice the use of two indexes next to each other) and the answer to question 3 of level one as test[1][3].answer.

The structure described here is known as a nested object construct. In this example, test is an object. It has a set of properties (all objects in this case) which can be referred to by their numerical index. So, test[1] is a property of test and an object in its own right. Because test[1] is an object, it can also have properties, in this case, referred to by numerical index. So, test[1][3] is a property of test[1] and, again, this property is itself an object. Once again, as an object, test[1][3] can have properties-in this case, answer, referenced by name as test[1][3].answer.

The next function in Listing 8.5 is the startTest() function:

parent.Register(self.name,"startTest");
function startTest(newLevel) {
   currentLevel=newLevel;
   currentQuestion=1;
   document.forms[0].answer.value="";
   document.forms[0].score.value=0;
   displayQuestion();
}

The startTest() function is one of the functions you register with the parent.Register() function from the hIdaho Frameset. You do this because you want to be able to call the function from the level frame.

The function accepts a single argument-the level of the new test- and sets currentLevel and currentQuestion appropriately as well as clearing the fields of the form. Then the function calls displayQuestion() to start the test.

function displayQuestion() {
   ask = test[currentLevel][currentQuestion].question;
   answer = test[currentLevel][currentQuestion].answer;
   toOutput = "" + currentQuestion + ". What is " + ask + "?";
   document.forms[0].answer.value = "";
   window.open("display.htm","output");
}

This function is used to display each successive question. It takes no arguments but gets its information from the global variables currentLevel and currentQuestion. In this way, it can get the current text of the question by using test[currentLevel][currentQuestion].question.

The function then stores the complete output in toOutput and uses the method window.open() to open display.htm in the frame named output. As you will learn later in the section on the window object, open() can be used to open files in named frames and windows.

parent.Register(self.name,"output");
function output() {
   return toOutput;
}

Like the startTest() function, you register the output() function with parent.Register(). The function simply returns the value of toOutput and is used to update the display in the output frame, as you will see when you look at the source code of the file display.htm.

function checkAnswer(form) {
   answer = form.answer.value;

   if (answer == "" || answer == null) {
      alert("Please enter an answer.");
       return;
   }

   correctAnswer = test[currentLevel][currentQuestion].answer;
   ask = test[currentLevel][currentQuestion].question;
   score = form.score.value;
   if (eval(answer) == correctAnswer) {
      toOutput = "Correct!";
      score ++;
      form.score.value = score;
   } else {
      toOutput = "Sorry! " + ask + " is " + correctAnswer + ".";
   }
   window.open("display.htm","output");
   if (currentQuestion < 10) {
      currentQuestion ++;
      setTimeout("displayQuestion()",3000);
   } else {
      toOutput = "You're Done!<BR>You're score is " + score + " out of 10.";
      setTimeout("window.open('display.htm','output')",3000);
      form.answer.value="";
      form.score.value="0";
   }
}

checkAnswer() is where the bulk of the work is done in the script.

Because checkAnswer() is called from the form, it takes a single argument for the form object. The function compares the student's answer stored in the field form.answer with the correct answer taken from the test object.

If the student answered correctly, an appropriate message is stored in toOutput, and the score is incremented and displayed. If the answer is wrong, an appropriate message is stored in toOutput, but the score is left untouched. Once the answer is checked, the message is displayed using window.open() to open display.htm in the output frame.

The function then uses the condition currentQuestion < 10 to check if the question just answered is the last question in the test. If it is not the last question, then the currentQuestion variable is increased by one to go to the next question and setTimeout() is used to wait three seconds (3000 milliseconds) before displaying the new question with displayQuestion().

Otherwise, the function stores the results of the test in toOutput, displays them with a similar three-second delay using setTimeout(), and then clears the answer and score fields of the form.

function welcome() {
   toOutput = "Welcome!";
   window.open("display.htm","output");
}

The function welcome() stores a welcome message in toOutput and then displays it by loading display.htm into the output frame.

<BODY BGCOLOR="#FFFFFF" TEXT="#0000FF" onLoad="welcome();">

After the document finishes loading, the welcome() function is called using the onLoad event handler.

Note
In the preceding segment of the BODY tag, you will notice the use of RGB triplets to define color for the background and text in the document. The RGB triplets (such as FFFFFF and 00FFFF) define colors as combinations of red, blue, and green. The six hexadecimal digits consist of three pairs of the form: RRGGBB. The use of colors in documents is discussed in more detail later in this chapter in the section about the document object.

<FORM METHOD=POST>
<CENTER>
<STRONG>Type You're Answer Here:</STRONG><BR>
<INPUT TYPE=text NAME=answer SIZE=30><P>
<INPUT TYPE=button NAME=done VALUE="Check Answer"
onClick="checkAnswer(this.form);"><P>
Correct Answers So Far:<BR>
<INPUT TYPE=text NAME=score VALUE="0" SIZE=10
onFocus="this.blur();">
</FORM>

This form is where most user interaction takes place-with the exception of the level frame. It has a text entry field named answer where users type their answers to each question, a button they click on to check their answers, and a text field to display the current score.

Only two event handlers are used: onClick="checkAnswer(this.form);" to check the users' answer and onFocus="this.blur;" to ensure users don't try to cheat by changing their own scores.

The file level.htm (Listing 8.6) contains a simple three button form to start a new test at any of the three levels:

<FORM METHOD=POST>
<INPUT TYPE=button NAME="one" VALUE="Level One"
onClick="parent.Exec('startTest',1);">
<INPUT TYPE=button NAME="two" VALUE="Level Two"
onClick="parent.Exec('startTest',2);">
<INPUT TYPE=button NAME="three" VALUE="Level Three"
onClick="parent.Exec('startTest',3);">
</FORM>

Each button has a similar onClick event handler which uses parent.Exec() to call the startTest() function from the form frame and pass it a single integer argument for the level.

The only other file involving JavaScript scripting is display.htm which sets up the body of the document and then uses document.write() to display the result returned by output() from the form frame. The function is called using parent.Exec(). In this way, every time the main script in form.htm reloads display.htm, the current value of toOutput is returned by output() and displayed as the body text for display.htm.

Note
You could have written the output directly to the output frame using document.write(). You will see an example of this later in the chapter.

The document Object

In any given window or frame, one of the primary objects is the document object. The document object provides the properties and methods to work with numerous aspects of the current document, including information about anchors, forms, links, the title, the current location and URL, and the current colors.

You already have been introduced to some of the features of the document object in the form of the document.write() and document.writeln() methods, as well as the form object and all of its properties and methods.

The document object is defined when the BODY tag is evaluated in an HTML page and the object remains in existence as long as the page is loaded. Because many of the properties of the document object are reflections of attributes of the BODY tag, you should have a complete grasp of all the attributes available in the BODY tag in Navigator 2.0.

The BODY Tag

The BODY tag defines the main body of an HTML document. Its attributes enable the HTML author to define colors for text and links, as well as background colors or patterns for the document.

In addition, as you have already learned, there are two event handlers, onLoad and onUnload, that can be used in the BODY tag.

The following is a list of available attributes for the BODY tag:

Properties of the document Object

Table 8.3 outlines the properties of the document object.

Table 8.3. Properties of the document object.

PropertyDescription
alinkColor The RGB value for the color of activated links expressed as a hexadecimal triplet.
anchors Array of objects corresponding to each named anchor in a document.
applets Array of objects corresponding to each Java applet included in a document. The applets array will be discussed in detail in Chapter 14.
bgColor The RGB value of the background color as a hexadecimal triplet.
cookie Contains the value of the cookies for the current document. Cookies are discussed in depth in Chapter 9.
embeds Array of objects reflecting each plug-in in a document. The embeds array will be discussed in detail in Chapter 14.
fgColor The RGB value of the foreground color as a hexadecimal triplet.
forms Array of objects corresponding to each form in a document.
images Array of objects corresponding to each in-line image included in   a document.
lastModified A string containing the last date the document was modified.
linkColor The RGB value of links as a hexadecimal triplet.
links An array of objects corresponding to each link in a document. Links can be hypertext links or clickable areas of an imagemap.
location The full URL of the document. This is the same as the URL property. URL should be used instead of location.
referrer Contains the URL of the document that called the current document.
title A string containing the title of the document.
URL The full URL of the document.
vlinkColor The RGB value of followed links as a hexadecimal triplet.

Some of these properties are obvious. For instance, in a document containing the tag

<BODY BGCOLOR="#FFFFFF" FGCOLOR="#000000"
LINK="#0000FF">

document.bgColor would have a value of "#FFFFFF", document.fgColor would equal "#000000", and document.linkColor would be "#0000FF".

In addition, you have already learned to use the forms array.

However, the anchors, images, and links arrays, along with the location object deserve a closer look.

The anchors Array

While the <A> tag in HTML is usually used to define hypertext links to other documents, it can also be used to define named anchors in a document so that links within a document can jump to other places in the document.

For instance, in the HTML page

<HTML>

<HEAD>
<TITLE>Anchors Example</TITLE>
</HEAD>

<BODY>
<A NAME="one">Anchor one is here
HTML Code
<A NAME="two">Anchor two is here
More HTML Code
<A HREF="#one">Go back to Anchor One</A><BR>
<A HREF="#two">GO back to Anchor Two</A>
</BODY>

</HTML>

two anchors are defined using <A NAME="anchorName"> (<A NAME="one"> and <A NAME="two">), and links to those anchors are created using <A HREF="#anchorName"> (<A HREF="#one"> and <A HREF="#two">).

JavaScript provides the anchors array as a means to access information and methods related to anchors in the current document. Each element in the array is an anchor object, and like all arrays, the array has a length property.

The order of anchors in the array follows the order of appearance of the anchors in the HTML document.

Therefore, in the example, document.anchors.length would have a value of 1 (since the anchors array, like the forms array and frames array starts with a zero index) and document.anchors[0] would refer to the anchor named one.

The images Array

The images array is implemented in Navigator 3.0 only. It was unavailable in Navigator 2.0.

Any image included in an HTML with the IMG tag is accessible through the images array. Like the anchors array, the images array has a length property.

Each entry in the array is an Image object, which has nine properties that reflect information about the image and information in the IMG tag:

Most of these properties are read-only-that is, their values cannot be set in a script. src and lowsrc, however, can be dynamically changed. The result of changing these values will update the display with the new image specified.

Note
When a new image is specified by changing the value of src or lowsrc it is scaled to fit into the space used by the original image.

Images also have three event handlers associated with them:

In addition to instances of the Image object in the images array, it is possible to create additional Image objects using the Image() constructor.

This causes an image to be loaded across the network, but the image will not be displayed until the object is assigned to one of the Image objects in the images array. To create an Image object using the constructor function, use the following syntax:

newImageName = new Image();
newImageName.src = "filename";

Then, the image could be displayed in place of an image already rendered to the window by using

document.images[index].src = myImage.src;

The links Array

Just as the anchors array provides a sequential list of all the anchors in a document, the links array offers an entry for each hypertext link defined by <A HREF="URL"> in an HTML document or as a clickable area in an imagemap using the AREA tag.

Also like the anchors array, each element in the links array is a link object or an Area object, and the array has a length property.

The link object has a several properties and event handlers, as defined in the following list:

The Area object offers the same properties and event handlers with the exception of onClick.

Methods of the document Object

In addition to the write() and writeln() methods which you have been using throughout the book, the document object provides three other methods: open(), close(), and clear().

The open() method is used to open the document window for writing a MIME type. It takes a single argument: the MIME type (such as text/html). You can also use the window.open() method to open a window or frame for writing a document, as you will see in the section on the window object.

MIME stands for Multi-purpose Internet Mail Extensions. MIME provides a way to exchange files in any format between computers using Internet mail standards. MIME supports pre-defined file types and allows the creation of custom types. MIME types are specified using two-part codes, such as text/html for HTML files and image/gif for GIF bitmap graphics.

Further more, close() closes the document window for writing, and the clear() method clears the current document window. Document output is not actually rendered (displayed) until document.close() is called.

Using the document Object in a Color Tester

For this example, you are going to build a simple utility to demonstrate what different combinations of text, link, and background colors look like.

The application will use two frames: the top frame contains five text entry fields for the background color, text color, link color, active link color, and followed link color, plus a button to enable users to test their color combinations.

When users press the button, the script loads a simple document, using the specified colors, into the lower frame. The users should be able to specify colors by hexadecimal triplets or by name.

To do this, you don't need to use the hIdaho Frameset you used in Listings 8.3 through 8.8 because you won't be using nested framesets or making cross-frame function calls.

The parent frameset is defined in Listings 8.9 and 8.10.


Listing 8.9. The parent frameset for the color tester.
<HTML>

<HEAD>
<TITLE>Example 8.9</TITLE>
</HEAD>

<FRAMESET ROWS="45%,*">
   <FRAME SRC="pick.htm">
   <FRAME SRC="blank.htm" NAME="output">
</FRAMESET>

</HTML>

The source code for the file pick.htm, where all the processing occurs, is in Listing 8.10.


Listing 8.10. The pick.htm file.
<HTML>

<HEAD>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FORM OTHER BROWSERS

function display(form) {
   doc = open("","output");
   doc.document.write ('<BODY BGCOLOR="' + form.bg.value);
   doc.document.write ('" TEXT="' + form.fg.value);
   doc.document.write ('" LINK="' + form.link.value);
   doc.document.write ('" ALINK="' + form.alink.value);
   doc.document.write ('" VLINK="' + form.vlink.value);
   doc.document.writeln ('">');
   doc.document.write("<H1>This is a test</H1>");
   doc.document.write("You have selected these colors.<BR>");
   doc.document.write('<A HREF="#">
      This is a test link</A>');
   doc.document.write("</BODY>");
   doc.document.close();
}

// STOP HIDING SCRIPT -->
</SCRIPT>

</HEAD>

<BODY>

<CENTER>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

document.write('<H1>The Colour Picker</H1>');
document.write('<FORM METHOD=POST>');
document.write('Enter Colors:<BR>');

document.write('Background: <INPUT TYPE=text NAME="bg"
VALUE="' + document.bgColor + '"> ... ');
document.write('Text: <INPUT TYPE=text NAME="fg"
>VALUE="' + document.fgColor + '"><BR>');
document.write('Link: <INPUT TYPE=text NAME="link"
VALUE ="' + document.linkColor + '"> ...');
document.write('Active Link: <INPUT TYPE=text NAME="alink"
VALUE="' + document.alinkColor + '"><BR>');
document.write('Followed Link: <INPUT TYPE="text" NAME="vlink"
VALUE ="' + document.vlinkColor + '"><BR>');
document.write('<INPUT TYPE=button VALUE="TEST"
onClick="display(this.form);">');

document.write('</FORM>');

display(document.forms[0]);

// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>

</CENTER>

</BODY>

</HTML>

The program produces results like those in Figure 8.7.

Figure 8.7 : With JavaScript, you can dynamically test color combinations.

As you can see in this example, all the work is being done in the top frame, which contains the document pick.htm.

The file has two main components: a JavaScript function and the body of the document, which is almost entirely generated by another JavaScript script using the document.write() and document.writeln() methods.

The interface consists of a single form containing five fields for each of the color values, plus a button that calls the function display().

function display(form) {
   doc = open("","output");
   doc.document.write ('<BODY BGCOLOR="' + form.bg.value);
   doc.document.write ('" TEXT="' + form.fg.value);
   doc.document.write ('" LINK="' + form.link.value);
   doc.document.write ('" ALINK="' + form.alink.value);
   doc.document.write ('" VLINK="' + form.vlink.value);
   doc.document.writeln ('">');
   doc.document.write("<H1>This is a test</H1>");
   doc.document.write("You have selected these colors.<BR>");
   doc.document.write('<A HREF="#">
      This is a test link</A>');

   doc.document.write("</BODY>");
   doc.document.close();
}

The function is fairly simple: An empty document is opened in the output frame using the window.open() method, and the name doc is assigned for JavaScript to refer to that window (frame).

The commands doc.document.write() and doc.document.writeln() can then be used to write HTML to the newly opened window. The values of the five form fields are then used to build a custom BODY tag that defines all the colors for the document. After the text has been output, the method doc.document.close() is used to close the open document and finish displaying it in the frame.

With this single function, you can build a simple form in the body of the document. The form is built by a JavaScript script that assigns initial values to the five fields using properties of the document object to set the values to the current browser defaults. Then the script calls display() so that an initial sample is displayed in the lower frame.

As with many programs, there is more than one way to achieve a desired effect. For instance, the display() function could be rewritten to change the colors dynamically-without rewriting the content of the frame-by using the color properties of the document object:

function display(form) {
   parent.output.document.bgColor = form.bg.value;
   parent.output.document.fgColor = form.fg.value;
   parent.output.document.linkClor = form.link.value;
   parent.output.document.alinkColor =
form.alink.value;
   parent.output.document.vlinkColor =
form.vlink.value;
}

Then you simply can remove the call to display() in the body of the HTML document, make the content of the output frame a separate HTML document, and load the sample document into the lower frame in the parent frameset.

The window Object

As you learned in Chapter 1, "Where Does JavaScript Fit In?" when you were first introduced to the Navigator Object Hierarchy, the window object is the parent object of each loaded document.

Because the window object is the parent object for loaded documents, you usually do not explicitly refer to the window object when referring to its properties or invoking its methods. For this reason, window.alert() can be called by using alert().

Table 8.4 outlines the properties and methods of the window object. You have seen many of these, including the frames array and the parent object, as well as the alert(), confirm(), open(), prompt(), and setTimeout() methods.

Table 8.4. Properties and methods of the window object.

NameDescription
frames Array of objects containing an entry for each child frame in a frameset document.
document The document object for the document currently loaded in the window.
location An object reflecting the current URL loaded in the window.
opener Refers to the window containing the document that opened the current document. This only has a value if the current window was opened or created with the open() method.
parent The FRAMESET in a FRAMESET-FRAME relationship.
self The current window-use this to distinguish between windows and forms of the same name.
top The top-most parent window.
status The value of the text displayed in the window's status bar. This can be used to display status messages to the user.
defaultStatus The default value displayed in the status bar.
alert() Displays a message in a dialog box with an OK button.
blur() Removes focus from a window. In most versions of Navigator, this sends the window to the background.
confirm() Displays a message in a dialog box with OK and Cancel buttons. This returns true when the user clicks on OK, false otherwise.
close() Closes the current window.
focus() Gives input focus to a window. In most versions of Navigator, this brings the window to the front.
open() Opens a new window with a specified document or opens the document in the specified named window.
prompt() Displays a message in a dialog box along with a text entry field.
scroll() Scrolls the window to a coordinate specified by an x,y coordinate passed as arguments to the method.
setTimeout() Sets a timer for a specified number of milliseconds and then evaluates an expression when the timer has finished counting. Program operation continues while the timer is counting down.
clearTimeout() Cancels a previously set timeout.

The location Object

The location object provides several properties and methods for working with the location of the current object.

Table 8.5 outlines these properties and methods.

Table 8.5. Properties and methods of the location object.

NameDescription
hash The anchor name (the text following a # symbol in an HREF attribute)
host The hostname and port of the URL
hostname The hostname of the URL
href The entire URL as a string
pathname The file path (the portion of the URL following the third slash)
port The port number of the URL (if there is no port number, then the empty string)
protocol The protocol part of the URL (such as http:, gopher: or ftp:-including the colon)
reload() Reloads the current URL
replace() Loads the a new URL over the current entry in the history list
search The form data or query following the question mark (?) in the URL

Working with the Status Bar

Using the status bar-the strip at the bottom of the Navigator window where you are told about the current status of document transfers and connections to remote sites-can be used by JavaScript programs to display custom messages to the user.

This is primarily done using the onMouseOver event handler, which is invoked when the user points at a hypertext link. By setting the value of self.status to a string, you can assign a value to the status bar (you could also use window.status or status here). In the program

<HTML>

<HEAD>
<TITLE>Status Example</TITLE>
</HEAD>

<BODY>
<A HREF="home.html" onMouseOver="self.status='Go Home!'; return true;">Home</A>
<A HREF="next.html" onMouseOver="self.status='Go to the next Page!';
return true;">Next</A>
</BODY>

</HTML>

two different messages are displayed when the user points the mouse at the links. This can be more informative than the URLs that Navigator normally displays when a user points at a link.

Note
Notice that both of the onMouseOver event handlers in the script return a true value after setting the status bar to a new value. This is necessary to display a new value in the status bar using the onMouseOver event handler.

Opening and Closing Windows

By using the open() and close() methods, you have control over what windows are open and which documents they contain.

The open() method is the more complex of the two. It takes two required arguments and an optional feature list in the following form:

open("URL", "windowName", "featureList");

Here the featureList is a comma-separated list containing any of the entries in Table 8.6.

Table 8.6. Windows features used in the open() method.

NameDescription
toolbar Creates the standard toolbar
location Creates the location entry field
directories Creates the standard directory buttons
status Creates the status bar
menubar Creates the menu at the top of the window
scrollbars Creates scroll bars when the document grows beyond the current window
resizable Enables resizing of the window by the user
copyhistory Indicates whether the history list of the current window should be copied to the new window
width Specifies the window width in pixels
height Specifies the window height in pixels

Note
With the exception of width and height, which take integer values, all of these features can be set to true with a value of yes or 1 or set to false with a value of no or 0.

For example, to open a document called new.html in a new window named newWindow and to make the window 200 pixels by 200 pixels with all window features available except resizable, you could use the command

window.open("new.html","newWindow","toolbar=yes,
location=1,directories=yes,status=yes,menubar=1,
scrollbars=yes,resizable=0,copyhistory=1,width=200,he
ight=200");

which would produce a window like the one in Figure 8.8.

Figure 8.8 : You control the size of new windows, as well as which elements to display.

Note that you can open a window and then write HTML into that window using document.writeln() and document.write(). You saw an example of this in Listings 8.6 through 8.8.

For instance, the function newwindow() opens a new window and writes several lines of HTML into it.

function newwindow() {
   newWindow = open("","New_Window");
   
   newWindow.document.write("<H1>Testing
...</H1>");
      newWindow.document.writeln("1... 2...
3...");
   newWindow.document.close();

}

Note
Notice the command newWindow = open("",:New Window"); which opens an instance of the window object and names it newWindow so that you can then use commands such as newWindow.document.write().

The close() method is simpler to use:

window.close();

simply closes the current window.

Pausing with Timeouts

You already saw an example of using setTimeout() in Bill Dortch's hIdaho Frameset earlier in this chapter where he suggests using a setTimeout() call to make sure a function is registered before trying to call the function.

The setTimeout() method takes the form

ID=setTimeout("expression",milliseconds)

where expression is any string expression, including a call to a function, milliseconds is the number of milliseconds-expressed as an integer-to wait before evaluating the expression, and ID is an identifier that can be used to cancel the setTimeout() before the expression is evaluated.

clearTimeout() is passed a single argument: the identifier of the timeout setting to be canceled.

For instance, if you want to create a page that displays a welcome message to the user and then automatically goes to a new page five seconds later if the user hasn't clicked on the appropriate button, you could write a script like Listing 8.11.


Listing 8.11. Creating an automatic pause.
<HTML>

<HEAD>
<TITLE>Timeout Example</TITLE>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

function go() {
   open("new.html","newWindow");
}

// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>

</HEAD>

<BODY onLoad="timeout = setTimeout('go()',5000);">
<IMG SRC="welcome.gif">
<H1>Click on the button or wait five seconds to continue ...</H1>
<FORM METHOD=POST>
<INPUT TYPE=button VALUE="Continue ..." onClick="clearTimeout(timeout); go();">
</FORM>
</BODY>

</HTML>

Creating a Status Bar Message Handler

In this example, you produce a simple function to implement status bar help in any HTML document. The function can be called from any event handler and will display a message in the status bar.

function help(message) {
   self.status = message;
   return true;
}

With this function, you can then implement full on-line pointers and help systems. For instance, if you use this in the math test from Listings 8.3 through 8.8, you can add help messages with only slight modifications to both form.htm and level.htm. See Listing 8.12 for the new version.


Listing 8.12. Updating the math test program.
<!-- SOURCE CODE OF form.htm -->
<HTML>

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

var currentLevel=1;
var currentQuestion=1;
var toOutput = "";

// DEFINE LEVEL ONE
q1 = new question("1 + 3",4);
q2 = new question("4 + 5",9);
q3 = new question("5 - 4",1);
q4 = new question("7 + 3",10);
q5 = new question("4 + 4",8);
q6 = new question("3 - 3",0);
q7 = new question("9 - 5",4);
q8 = new question("8 + 1",9);
q9 = new question("5 - 3",2);
q10 = new question("8 - 3",5);
levelOne = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE LEVEL TWO
q1 = new question("15 + 23",38);
q2 = new question("65 - 32",33);
q3 = new question("99 + 45",134);
q4 = new question("34 - 57",-23);
q5 = new question("-34 - 57",-91);
q6 = new question("23 + 77",100);
q7 = new question("64 + 32",96);
q8 = new question("64 - 32",32);
q9 = new question("12 + 34",46);
q10 = new question("77 + 77",154);
levelTwo = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE LEVEL THREE
q1 = new question("10 * 7",70);
q2 = new question("15 / 3",5);
q3 = new question("34 * 3",102);
q4 = new question("33 / 2",16.5);
q5 = new question("100 / 4",25);
q6 = new question("99 / 6",16.5);
q7 = new question("32 * 3",96);
q8 = new question("48 / 4",12);
q9 = new question("31 * 0",0);
q10 = new question("45 / 1",45);
levelThree = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

// DEFINE TEST
test = new newTest(levelOne,levelTwo,levelThree);

function newTest(levelOne,levelTwo,levelThree) {
   this[1] = levelOne;
   this[2] = levelTwo;
   this[3] = levelThree;
}

function level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10) {
   this[1] = q1;
   this[2] = q2;
   this[3] = q3;
   this[4] = q4;
   this[5] = q5;
   this[6] = q6;
   this[7] = q7;
   this[8] = q8;
   this[9] = q9;
   this[10] = q10;
}

function question(question,answer) {
   this.question = question;
   this.answer = answer;
}

parent.Register(self.name,"startTest");
function startTest(newLevel) {
   currentLevel=newLevel;
   currentQuestion=1;
   document.forms[0].answer.value="";
   document.forms[0].score.value=0;
   displayQuestion();
}

function displayQuestion() {
   ask = test[currentLevel][currentQuestion].question;
   answer = test[currentLevel][currentQuestion].answer;
   toOutput = "" + currentQuestion + ". What is " + ask + "?";
   document.forms[0].answer.value = "";
   window.open("display.htm","output");
}

parent.Register(self.name,"output");
function output() {
   return toOutput;
}

function checkAnswer(form) {
   answer = form.answer.value;
   correctAnswer = test[currentLevel][currentQuestion].answer;
   ask = test[currentLevel][currentQuestion].question;
   score = form.score.value;
   if (eval(answer) == correctAnswer) {
      toOutput = "Correct!";
      score ++;
      form.score.value = score;
   } else {
      toOutput = "Sorry! " + ask + " is " + correctAnswer + ".";
   }
   window.open("display.htm","output");
   if (currentQuestion < 10) {
      currentQuestion ++;
      setTimeout("displayQuestion()",3000);
   } else {
      toOutput = "You're Done!<BR>You're score is " + score + " out of 10.";
      setTimeout("window.open('display.htm','output')",3000);
      form.answer.value="";
      form.score.value="0";
   }
}

function welcome() {
   toOutput = "Welcome!";
   window.open("display.htm","output");
}

parent.Register(self.name,"help");
function help(message) {
   self.status = message;
   return true;
}

// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>

</HEAD>

<BODY BGCOLOR="#FFFFFF" TEXT="#0000FF" onLoad="welcome();">

<FORM METHOD=POST>
<CENTER>
<STRONG>Type You're Answer Here:</STRONG><BR>
<INPUT TYPE=text NAME=answer SIZE=30
onFocus="help('Enter your answer here.');"><P>
<A HREF="#" onClick="checkAnswer(document.forms[0]);"
onMouseOver="return help('Click here to check your answer.');">
Check Answer</A><P>
<INPUT TYPE=text NAME=score VALUE="0" SIZE=10 onFocus="this.blur();">
</FORM>

</BODY>

</HTML>

Similarly, level.htm requires changes:


Listing 8.13. Updating the level.htm file.
<!-- SOURCE CODE OF level.htm -->
<HTML>

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

function help(message) {
   self.status = message;
   return true;
}

// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>

</HEAD>

<BODY BGCOLOR="#000000" TEXT="#FFFFFF" LINK="#FFFFFF"
ALINK="#FFFFFF" VLINK="#FFFFFF">
<CENTER>
<STRONG>
Select a level here:<BR>
<A HREF="#" onClick="parent.Exec('startTest',1);"
onMouseOver="return
parent.Exec('help','Start test at level one.');">LEVEL ONE</A>
<A HREF="#" onClick="parent.Exec('startTest',2);"
onMouseOver="return parent.Exec('help',
'Start test at level two.');">LEVEL TWO</A>
<A HREF="#" onClick="parent.Exec('startTest',3);"
onMouseOver="return parent.Exec('help',
'Start test at level three.');">LEVEL THREE</A>
</STRONG>
</CENTER>
</BODY>

</HTML>

These changes produce results like those in Figure 8.9.

Figure 8.9 : Using onMouseOver and the status property to display help messages in the navigator window's status bar.

In order to implement the interactive help in the math test, you have to make only minor changes to both HTML files.

In form.htm, you have added the help() function to the header and made two changes in the body of the document. You have added an onFocus event handler to the answer field. The event handler calls help() to display a help message.

You have also changed the button to a hypertext link so that you can use the onMouseOver event handler to display another help message. There are several points to note in the following line:

<A HREF="#" onClick="checkAnswer(document.forms[0]);"
onMouseOver="return help('Click here to check your
answer.');">
Check Answer</A>

First, in the call to checkAnswer(), you can't pass the argument this.form because the hypertext link is not a form element. For this reason, you use document.forms[0] to explicitly identify the form.

The second point to notice is that you have an empty anchor in the attribute HREF="#". When a user clicks on the link, only the onClick event handler executes, but because there is no URL specified, the page doesn't change.

Note
For changes to the status bar to take effect in an onMouseOver event, you need to return a value of true from the event handler. This isn't true in other event handlers, such as onFocus.

Tip
Instead of using the onClick event handler, you can use a special type of URL to call JavaScript functions and methods:
<A HREF="JavaScript:checkAnswer(document.forms[0])">.

In level.htm, you make similar changes. You simply have added the help function to the file's header and changed the three form buttons to three hypertext links with appropriate onMouseOver event handlers.

Note
Notice when you try these scripts that status messages stay displayed until another message is displayed in the status bar, even when the condition that caused the message to be displayed has ended.

Colors in Navigator

The various HTML color attributes and tags (BGCOLOR, FGCOLOR, VLINKCOLOR, ALINKCOLOR, LINKCOLOR, FONT COLOR) and their related JavaScript methods (bgColor(), fgColor(), vlinkColor(), alinkColor(), linkColor(), fontColor()) can take both RGB triplets and selected color names as their values.

Table 8.7 is a list of selected Netscape color words and their corresponding RGB triplets. The complete list of color words can be found in the JavaScript document at the Netscape Web site (see appendix A).

Table 8.7. Color words in Navigator 2.0.

Color Name
RGB Triplet
Color Name
RGB Triplet
antiquewhite
FA EB D7
ivory
FF FF F0
aqua
00 FF FF
Courier">lemonchiffon
FF FA CD
azure
F0 FF FF
lightblue
AD D8 E6
beige
f5 f5 DC
lightyellow
FF FF E0
black
00 00 00
magenta
FF 00 FF
blue
00 00 FF
maroon
80 00 00
brown
A5 2A 2A
mediumpurple
93 70 DB
chartreuse
7F FF 00
mediumturquoise
48 D1 cc
cornflowerblue
64 95 ED
moccasin
FF E4 B5
crimson
DC 14 3C
navy
00 00 80
darkcyan
00 8B 8B
orange
FF A5 00
darkgray
A9 A9 A9
papayawhip
FF EF D5
darkgreen
00 64 00
pink
FF C0 CB
darkpink
FF 14 93
rosybrown
BC 8F 8F
firebrick
B2 22 22
salmon
FA 80 72
floralwhite
FF FA F0
silver
C0 C0 C0
fuchsia
FF 00 FF
slateblue
6A 5A CD
gold
FF D7 00
tan
D2 B4 8C
greenyellow
AD FF 2F
tomato
FF 63 47
hotpink
FF 69 B4
yellow
FF FF 00
indigo
4B 00 82
 
 

Summary

In this chapter you have covered several significant topics.

You now know how to divide the Navigator window into multiple independent sections and how to work with functions and values in different windows using the hIdaho Frameset.

In addition, you have taken a detailed look at the document object and learned about its properties, which give you information about colors used in a document, as well as information about the last modification date of a document, and the location of a document.

The window object, which is the parent object of the document object, provides you the ability to work with various aspects of windows and frames, including altering the text displayed in the window's status bar, setting timeouts to pause before evaluating expressions or calling functions, and opening and closing named windows.

In the Chapter 9, "Remember Where You've Been with Cookies," you are going to take a look at Cookies-a feature of Navigator that enables you to store information about a page and recall it later when the user returns to the page.

Commands and Extensions Review

Command/ExtensionType Description
FRAMESET HTML tagDefines a window or frame containing frames
ROWS HTML attributeDefines the number of rows in a FRAMESET tag
COLS HTML attributeDefines the number of columns in a FRAMESET tag
FRAME HTML tagDefines the source document for a frame defined in a FRAMESET container
SRC HTML attributeIndicates the URL of a document to load into a frame
NORESIZE HTML attributeSpecifies that a frame is fixed in size and cannot be resized by the user
SCROLLING HTML attributeIndicates whether scroll bars are to be displayed in a frame (takes the value YES, NO or AUTO)
MARGINHEIGHT HTML attributeSpecifies the vertical offset in pixels from the border of the frame
MARGINWIDTH HTML attributeSpecifies the horizontal offset in pixels from the border of the frame
TARGET HTML attributeIndicates the frame or window for a document to load into-used with the A, FORM, BASE, and AREA tags
NOFRAMES HTML tagIndicates HTML code to be displayed in browsers that don't support frames; used in documents containing the FRAMESET container tags
frames JavaScript propertyArray of objects for each frame in a Navigator window
parent JavaScript propertyIndicates the parent frameset document of the currently loaded document
BODY HTML tagDefines the main body of an HTML document
BACKGROUND HTML attributeSpecifies URL of a background image in the BODY tag
BGCOLOR HTML attributeSpecifies the background color for a document as a hexadecimal triplet or color name
FGCOLOR HTML attributeSpecifies the foreground color for a document as a hexadecimal triplet or color name
LINK HTML attributeSpecifies the color of link text
ALINK HTML attributeSpecifies the color of active link text
VLINK HTML attributeSpecifies the color of followed link text
alinkColor JavaScript propertyThe color value of active links
anchors JavaScript propertyArray of objects corresponding to each named anchor in a document
applets JavaScript propertyArray of objects corresponding to each Java applet in a document
bgColor JavaScript propertyThe background color value of a document
embeds JavaScript propertyArray of objects corresponding to each plug-in in a document
fgColor JavaScript propertyThe foreground color value of a document
forms JavaScript propertyArray of objects corresponding to each form in a document
images JavaScript PropertyArray of objects corresponding to each image in a document
lastModified JavaScript propertyAs a string, the last date the document was modified
linkColor JavaScript propertyThe color value of link text
links JavaScript propertyArray of objects corresponding to each link in a document
location JavaScript propertyObject defining the full URL of the document
title JavaScript propertyTitle of the document represented as a string
vlinkColor JavaScript propertyThe color value of followed links
hash JavaScript propertyAn anchor name (location object)
host JavaScript propertyHostname and port of a URL (location object)
href JavaScript propertyHostname of a URL (location object)
opener JavaScript propertyRefers to the window containing the script that opened the current window (window object)
pathname JavaScript propertyFile path from the URL (location object)
port JavaScript propertyPort number from the URL (location object)
protocol JavaScript propertyProtocol part of the URL (location object)
search JavaScript propertyForm data or query from the URL (location object)
URL JavaScript propertyThe full URL of a document
blur() JavaScript methodRemoves input focus from a window (window object)
open() JavaScript methodOpens a document for a particular MIME type (document object)
close() JavaScript methodCloses a document for writing (document object)
clear() JavaScript methodClears a document window (document object)
focus() JavaScript methodGives input focus to a window (window object)
reload() JavaScript methodReloads the current URL
replace() JavaScript methodLoads a new URL in the place of the current document
scroll() JavaScript methodScrolls the window to a specified location
self JavaScript propertyRefers to the current window
top JavaScript propertyThe top-most parent window
status JavaScript propertyText displayed in the status bar represented as a string
defaultStatus JavaScript propertyDefault text displayed in the status bar
close() JavaScript methodCloses the window (window object)
open() JavaScript methodOpens a document in a named window (window object)
setTimeout() JavaScript methodPauses for a specified number of milliseconds and then evaluates an expression
clearTimeout() JavaScript methodCancels a previously set timeout
onMouseOver Event handlerSpecifies script to execute when the mouse pointer is over a hypertext link
location JavaScript propertyThe location of the document currently loaded in a window

Q&A

QIf I use frames in my documents, will users of any other Web browsers be able to view my documents?
AAt the present time, only Netscape Navigator supports the FRAMESET and FRAMES tags. If you make effective use of the NOFRAMES tag, it is possible to design perfectly reasonable non-frame alternatives for the users of other browsers' software. Of course, if you are using JavaScript, your users must be using Navigator.
QIs it possible to force changes in the relative size of frames using JavaScript?
ANo. There are no mechanisms to force resizing of frames in JavaScript without reloading the document and dynamically changing the value of the ROWS and COLS attributes of the FRAMESET tag using document.write() or document.writeln().
QIs it possible to set the status bar when the user points at a form button?
ANo. At the present time, the <INPUT TYPE=button> tag does not support the onMouseOver event, which you use in the <A> tag to set the status bar when the mouse points at the link.

Exercises

  1. Expand the math test example so that it presents the questions in a random order each time the user runs through the test.
  2. Design a program that does the following:
    Splits the screen into two frames.
    In the first, enables the user to indicate a URL.
    Loads the URL into the second frame, and once it's loaded, displays the following information about it in the first frame: all color attributes, the title of the document, and the last date of modification.
  3. What happens on the status bar in this script?

    <HTML>

    <HEAD>
    <TITLE>Exercise 8.3</TITLE>

    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FROM OTHER BROWSERS

    function help(message) {
       self.status = message;
       return true;
    }

    function checkField(field) {
       if (field.value == "")
          help("Remember to enter a value in this field");
       else
          help("");
       return true;
    }
    // STOP HIDING FROM OTHER BROWSERS -->
    </SCRIPT>

    </HEAD>

    <BODY>

    <FORM METHOD=POST>
    Name: <INPUT TYPE=text NAME="name" onFocus="help('Enter your name');"
                       onBlur="checkField(this);">
    Email: <INPUT TYPE=text NAME="email"
    onFocus="help('Enter your email address');"
                         onBlur="checkField(this);">
    </FORM>

    </BODY>

    </HTML>
  4. Extend Listing 8.9 and Listing 8.10 so that the user can specify any URL to be displayed using the specified color scheme. You will want to consider using the alternative method for the display() function.

Answers

  1. In order to add random order to the tests, you need to add two things to the program: a function to produce a suitable random number and a method to keep track of which questions have already been asked. All the changes are to form.htm and should look like this:

    <!-- SOURCE CODE OF form.htm -->
    <HTML>

    <HEAD>
    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FROM OTHER BROWSERS

    var currentLevel=1;
    var currentQuestion=1;
    var askedQuestion=0;
    var toOutput = "";

    // DEFINE LEVEL ONE
    q1 = new question("1 + 3",4);
    q2 = new question("4 + 5",9);
    q3 = new question("5 - 4",1);
    q4 = new question("7 + 3",10);
    q5 = new question("4 + 4",8);
    q6 = new question("3 - 3",0);
    q7 = new question("9 - 5",4);
    q8 = new question("8 + 1",9);
    q9 = new question("5 - 3",2);
    q10 = new question("8 - 3",5);
    levelOne = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

    // DEFINE LEVEL TWO
    q1 = new question("15 + 23",38);
    q2 = new question("65 - 32",33);
    q3 = new question("99 + 45",134);
    q4 = new question("34 - 57",-23);
    q5 = new question("-34 - 57",-91);
    q6 = new question("23 + 77",100);
    q7 = new question("64 + 32",96);
    q8 = new question("64 - 32",32);
    q9 = new question("12 + 34",46);
    q10 = new question("77 + 77",154);
    levelTwo = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

    // DEFINE LEVEL THREE
    q1 = new question("10 * 7",70);
    q2 = new question("15 / 3",5);
    q3 = new question("34 * 3",102);
    q4 = new question("33 / 2",16.5);
    q5 = new question("100 / 4",25);
    q6 = new question("99 / 6",16.5);
    q7 = new question("32 * 3",96);
    q8 = new question("48 / 4",12);
    q9 = new question("31 * 0",0);
    q10 = new question("45 / 1",45);
    levelThree = new level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10);

    // DEFINE TEST
    test = new newTest(levelOne,levelTwo,levelThree);

    function newTest(levelOne,levelTwo,levelThree) {
       this[1] = levelOne;
       this[2] = levelTwo;
       this[3] = levelThree;
    }

    function level(q1,q2,q3,q4,q5,q6,q7,q8,q9,q10) {
       this[1] = q1;
       this[2] = q2;
       this[3] = q3;
       this[4] = q4;
       this[5] = q5;
       this[6] = q6;
       this[7] = q7;
       this[8] = q8;
       this[9] = q9;
       this[10] = q10;
    }

    function question(question,answer) {
       this.question = question;
       this.answer = answer;
    }

    parent.Register(self.name,"startTest");
    function startTest(newLevel) {
       currentLevel=newLevel;
       currentQuestion=1;
       clearArray(asked);
       askedQuestion = chooseQuestion();
       document.forms[0].answer.value="";
       document.forms[0].score.value=0;
       displayQuestion();
    }

    function displayQuestion() {
       ask = test[currentLevel][askedQuestion].question;
       answer = test[currentLevel][askedQuestion].answer;
       toOutput = "" + currentQuestion + ". What is " + ask + "?";
       document.forms[0].answer.value = "";
       window.open("display.htm","output");
    }

    parent.Register(self.name,"output");
    function output() {
       return toOutput;
    }

    function checkAnswer(form) {
       answer = form.answer.value;
       correctAnswer = test[currentLevel][askedQuestion].answer;
       ask = test[currentLevel][askedQuestion].question;
       score = form.score.value;
       if (eval(answer) == correctAnswer) {
          toOutput = "Correct!";
          score ++;
          form.score.value = score;
       } else {
          toOutput = "Sorry! " + ask + " is " + correctAnswer + ".";
       }
       window.open("display.htm","output");
       if (currentQuestion < 10) {
          currentQuestion ++;
          askedQuestion = chooseQuestion();
          setTimeout("displayQuestion()",3000);
       } else {
          toOutput = "You're Done!<BR>You're score is " +
    Âscore + " out of 10.";
          setTimeout("window.open('display.htm','output')",3000);
          form.answer.value="";
          form.score.value="0";
       }
    }

    function welcome() {
       toOutput = "Welcome!";
       window.open("display.htm","output");
    }

    asked = new createArray(10);

    function createArray(num) {
       for (var j=1; j<=num; j++)
          this[j] = false;
       this.length=num;
    }

    function clearArray(toClear) {
       for (var j=1; j<=toClear.length; j++)
          toClear[j] = false;
    }

    function chooseQuestion() {
       choice = (getNumber() % 10) + 1;
       while (asked[choice]) {
          choice = (getNumber() % 10) + 1;
       }
       asked[choice] = true;
       return choice;
    }

    function getNumber() {
       var time = new Date();
       return time.getSeconds();
    }

    // STOP HIDING FROM OTHER BROWSERS -->
    </SCRIPT>

    </HEAD>

    <BODY BGCOLOR="#FFFFFF" TEXT="#0000FF" onLoad="welcome();">

    <FORM METHOD=POST>
    <CENTER>
    <STRONG>Type You're Answer Here:</STRONG><BR>
    <INPUT TYPE=text NAME=answer SIZE=30><P>
    <INPUT TYPE=button NAME=done VALUE="Check Answer"
    onClick="checkAnswer(this.form);"><P>
    Correct Answers So Far:<BR>
    <INPUT TYPE=text NAME=score VALUE="0" SIZE=10>
    </FORM>

    </BODY>

    </HTML>

    You've added four functions to the script, as well as made a few other subtle changes. First, you've added a simple createArray() function that enables you to create the asked array. You use this array to keep track of questions already asked. Each element is set to false until that question is asked.

    The clearArray() function takes an array as an argument and simply sets each element to false.

    The chooseQuestion() function adds the ability to randomly select a question. The function uses the getNumber() function (which returns the seconds from the current time) to create a pseudo-random number. The while loop keeps selecting numbers until it finds one that has a false entry in the asked array.

    Once an available question has been found, the appropriate entry in asked is set to true, and the number of the question is returned.

    In addition to these three functions, you have made some other small changes. You have added a global variable called askedQuestion. This variable indicates the index of the question you have asked. currentQuestion becomes the sequential number of the question to be displayed to the user.

    In the startTest() function, you add the lines

    clearArray(asked);
    askedQuestion = chooseQuestion();

    which clear the asked array to false and then select a question.

    In displayQuestion(), you have switched to using askedQuestion in place of currentQuestion as an index to the test[currentLevel] object, and in the function checkAnswer(), you have made the same change.

    In checkAnswer(), you have also changed the way the function selects the next question:

    currentQuestion ++;
    askedQuestion = chooseQuestion();
    setTimeout("displayQuestion()",3000);

    This simply chooses a random question and then calls displayQuestion().

  2. The following frameset and HTML document produce the desired results, as shown in Figure 8.10.

    Figure 8.10 : Testing colors on a document selected by the user.

    <!-- MAIN FRAMESET FILE -->

    <HTML>

    <HEAD>
    <TITLE>Exercise 8.2</TITLE>
    </HEAD>

    <FRAMESET COLS="25%,*">
       <FRAME SRC="info.htm">
       <FRAME SRC="blank.htm" NAME="display">
    </FRAMESET>

    </HTML>

    The HTML document would look like this:

    <!-- SOURCE CODE FOR info.htm -->

    <HTML>

    <HEAD>
    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FROM OTHER BROWSERS

    function loadSite(form) {
       var url = form.url.value;
       doc = open(url,"display");
       form.title.value = doc.document.title;
       form.date.value = doc.document.lastModified;
       form.bg.value = doc.document.bgColor;
       form.fg.value = doc.document.fgColor;
       form.link.value = doc.document.linkColor;
       form.alink.value = doc.document.alinkColor;
       form.vlink.value = doc.document.vlinkColor;
    }

    // STOP HIDING FROM OTHER BROWSERS -->
    </SCRIPT>
    </HTML>

    <BODY>

    <FORM METHOD=POST>
    URL: <INPUT TYPE=text NAME="url"><P>
    <INPUT TYPE=button NAME=load VALUE="Load URL"
    onClick="loadSite(this.form);"><P>
    Title: <INPUT TYPE=text NAME="title"
    onFocus="this.blur();"><P>
    Last Modified: <INPUT TYPE=text NAME="date" onFocus="this.blur();"><P>
    Background Color: <INPUT TYPE=text NAME="bg"
    onFoucs="this.blur();"><P>
    Text Color: <INPUT TYPE=text NAME="fg" onFocus="this.blur();"><P>
    Link Color: <INPUT TYPE=text NAME="link" onFocus="this.blur();"><P>
    Active Link Color: <INPUT TYPE=text NAME="alink"
    onFocus="this.blur();"><P>
    Followed Link Color: <INPUT TYPE=text NAME="vlink"
    onFocus="this.blur();">
    </FORM>

    </BODY>
    </HTML>

    You use one simple function to achieve the desired result: loadSite(). The function loads the specified URL into the display frame using window.open().

    Once this is done, you can use the properties of the document object to display the desired information into fields in the HTML form.

    In the form in the body of the HTML document, you use the onClick event handler in the button to call the loadSite() function. The display fields for the information about the document all have the event handler onFocus="this.blur();" to make sure the user can't alter the information.

  3. This script partially addresses the problem in Listings 8.12 and 8.13-that the help messages remain displayed even after you remove mouse focus from a link or remove focus from a field. This particular script displays a help message when the user gives focus to a field. When focus is removed, either a warning message is displayed or the status bar is cleared.
  4. The following script makes the necessary changes to enable the user to specify a URL to be displayed in the lower frame:

    <HTML>

    <HEAD>

    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FORM OTHER BROWSERS

    function display(form) {
       parent.output.document.bgColor = form.bg.value;
       parent.output.document.fgColor = form.fg.value;
       parent.output.document.linkClor = form.link.value;
       parent.output.document.alinkColor = form.alink.value;
       parent.output.document.vlinkColor = form.vlink.value;
    }

    function loadPage(url) {
       var toLoad = url.value;
       if (url.value == "")
          toLoad = "sample.htm";
       open (toLoad,"output");
    }

    // STOP HIDING SCRIPT -->
    </SCRIPT>

    </HEAD>

    <BODY>

    <CENTER>

    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FROM OTHER BROWSERS

    document.write('<H1>The Color Picker</H1>');
    document.write('<FORM METHOD=POST>');
    document.write('Enter Colors:<BR>');

    document.write('Background: <INPUT TYPE=text NAME="bg"
    VALUE="' + document.bgColor + '"> ... ');
    document.write('Text: <INPUT TYPE=text NAME="fg"
    VALUE="' + document.fgColor + '"><BR>');
    document.write('Link: <INPUT TYPE=text NAME="link"
    VALUE ="' + document.linkColor + '"> ...');
    document.write('Active Link: <INPUT TYPE=text NAME="alink"
    VALUE="' + document.alinkColor + '"><BR>');
    document.write('Followed Link: <INPUT TYPE="text" NAME="vlink"
    VALUE ="' + document.vlinkColor + '"><BR>');
    document.write('Test URL: <INPUT TYPE="text" SIZE=40 NAME="url"
    VALUE="" onChange="loadPage(this);"><BR>');
    document.write('<INPUT TYPE=button VALUE="TEST"
    onClick="display(this.form);">');

    document.write('</FORM>');

    // STOP HIDING FROM OTHER BROWSERS -->
    </SCRIPT>

    </CENTER>

    </BODY>

    </HTML>

    You have made two simple changes to the script. In addition to the display() function, you have added the loadPage() function, which loads a URL into the lower frame using window.open(). If the user provides no value for the URL, the standard sample file is loaded.

    In the form, you have added a text field for the URL with an onChange event handler that calls loadPage(). You also need to change the frameset to load the appropriate sample page into the lower frame:

    <HTML>

    <HEAD>
    <TITLE>Exercise 8.4</TITLE>
    </HEAD>

    <FRAMESET ROWS="45%,*">
       <FRAME SRC="pick.htm">
       <FRAME SRC="sample.htm" NAME="output">
    </FRAMESET>

    </HTML>