Previous Page TOC Index Next Page Home


8

Frames, Documents, and Windows

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 and 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.

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.


New Term

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:


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 OK to define the width of a column as 700 pixels, but to a user running at standard 640x480 VGA resolution, this frame would be wider than his display allows.

<FRAMESET ROWS="35%,*">

This 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).


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

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.


New Term

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://wwww.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 file name 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.

Attribute


Description


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 if 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 2.0 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 which 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 divides 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:

Input

<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>

Analysis

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.

End of Analysis

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 2.0 browser but will be displayed by any other browser.

For instance,

<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 but like Figure 8.4 in another browser.


Figure 8.3. Only Navigator 2.0 recognizes the FRAMESET tag.


Figure 8.4. Using 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 targetting is over-ridden by using a TARGET attribute in an A tag or FORM tag in the body of the HTML document.


Naming and targeting are not only relevant to frames. 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.

Value


Description


_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] and the index of the last frame as parent.frames.length.

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 must be able to communicate between the frames.


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.3 you build a simple calculator to evaluate expressions entered by users and use frames to display the output.

<!-- HTML CODE FOR PARENT FRAMESET (this is a separate file) -->

<HTML>

<HEAD>

<TITLE>Listing 8.3</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. 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 get a little bit more complicated.

When building nested framesets, it is possible to use sub-documents 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:

<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>


Figure 8.5. Using nested frameset produces complex screen layouts.

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 be referencing 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 the 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 it is possible to 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 re-evaluated.

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").

NOTE:

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 be called again.

NOTE:

setTimeout() is a method of the window object which 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 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.4) and work.htm (Listing 8.5) 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">):

Input

<!-- 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>

Input

<!-- 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 is kept in the file form.htm (Listing 8.6). form.htm is one of the frames in the nested frameset.

Input

<!-- 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.7) provides users with three buttons to select different levels. level.htm is the bottom frame in the parent frameset:

Input

<!-- 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.8):

Input

<!-- 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.9) contains the information displayed in the top frame of the parent frameset:

Input

<!-- SOURCE CODE OF title.htm -->

<HTML>

<BODY BGCOLOR="#000000" TEXT="#00FFFF">

<CENTER>

<H1>

<STRONG>

The Math Test

</STRONG>

</H1>

</CENTER>

</BODY>

</HTML>

Output

The final product would look something like Figure 8.6.


Figure 8.6. Using nested framesets to produce a multi-level math test.

End of Output

Analysis

As you can see in the source code listings, the file form.htm (Listing 8.6) 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.6 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 the 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.


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.7) 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.


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, it is important to 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.

Property


Description


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.

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.

fgColor

The RGB value of the foreground color as a hexadecimal triplet.

forms

Array of objects corresponding to each form 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.

location

An object defining the full URL of the document.

referrer

Contains the URL of the document which called the current document.

title

A string containing the title 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.

The anchors and links arrays, along with the location object, however, 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 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.

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

The link object has a single property, target, which contains a string with the name of the window or frame indicated in the link's TARGET attribute in the HTML file. For instance, in the following simple HTML file, document.links[0].target would have a value of "test":

<HTML>

<HEAD>

<TITLE>Links Example</TITLE>

</HEAD>

<BODY>

<A HREF="URL" TARGET="test">Just Testing</A>

</BODY>

</HTML>
The location Object

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

Table 8.4 outlines these properties and methods.

Name


Description


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)

search

The form data or query following the question mark (?) in the URL

assign()

Sets location.href

toString()

Returns location.href as a string

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.


New Term

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, as well as allowing 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.

By comparison, 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.4 through 8.9, because you won't be using nested framesets or making cross-frame function calls.

The parent frameset is defined as follows:

Input

<HTML>

<HEAD>

<TITLE>Example 8.2</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.11.

Input

<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>

Output

The program produces results like those in Figure 8.7.


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

End of Output

Analysis

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 which 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 which 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.

End of Analysis

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.5 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.

Name


Description


frames

Array of objects containing an entry for each child frame in a frameset document.

parent

The FRAMESET in a FRAMESET-FRAME relationship

self

The current window—uses 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()

Display a message in a dialog box with an OK button.

confirm()

Display 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.

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.

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.

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.


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

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.

Name


Description


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

width

Specifies the window width in pixels

height

Specifies the window height in pixels


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,height=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.

It is interesting to 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 Example 8.2.

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();

}

Notice the command newWindow = open("",:New Window"); which opens an instance of the window object and names it newWindow so that we can then use commands like 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 which 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 this:

Input

<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 Listing 8.4 through 8.9, you can add help messages with only slight modifications to both form.htm and level.htm.

Input

<!-- 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:

Input

<!-- 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>

Output

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.

End of Output

Analysis

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.


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.


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.


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

Color in Navigator 2.0

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 select 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).

Color Name


RGB Triplet


antiquewhite

FA EB D7

aqua

00 FF FF

azure

F0 FF FF

beige

F5 F5 DC

black

00 00 00

blue

00 00 FF

brown

A5 2A 2A

chartreuse

7F FF 00

cornflowerblue

64 95 ED

crimson

DC 14 3C

darkcyan

00 8B 8B

darkgray

A9 A9 A9

darkgreen

00 64 00

darkpink

FF 14 93

firebrick

B2 22 22

floralwhite

FF FA F0

fuchsia

FF 00 FF

gold

FF D7 00

greenyellow

AD FF 2F

hotpink

FF 69 B4

indigo

4B 00 82

ivory

FF FF F0

lemonchiffon

FF FA CD

lightblue

AD D8 E6

lightyellow

FF FF E0

magenta

FF 00 FF

maroon

80 00 00

mediumpurple

93 70 DB

mediumturquoise

48 D1 CC

moccasin

FF E4 B5

navy

00 00 80

orange

FF A5 00

papayawhip

FF EF D5

pink

FF C0 CB

rosybrown

BC 8F 8F

salmon

FA 80 72

silver

C0 C0 C0

slateblue

6A 5A CD

tan

D2 B4 8C

tomato

FF 63 47

yellow

FF FF 00

Summary

In this chapter you have covered several significant topics.

You now know how to divide the Navigator window into multiple independent sections and 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 2.0 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/Extension


Type


Description


FRAMESET

HTML tag

Defines a window or frame containing frames

ROWS

HTML attribute

Defines the number of rows in a FRAMESET tag

COLS

HTML attribute

Defines the number of columns in a FRAMESET tag

FRAME

HTML tag

Defines the source document for a frame defined in a FRAMESET container

SRC

HTML attribute

Indicates the URL of a document to load into a frame

NORESIZE

HTML attribute

Specifies that a frame is fixed in size and cannot be resized by the user

SCROLLING

HTML attribute

Indicates if scroll bars are to be displayed in a frame (takes the value YES, NO or AUTO)

MARGINHEIGHT

HTML attribute

Specifies the vertical offset in pixels from the border of the frame

MARGINWIDTH

HTML attribute

Specifies the horizontal offset in pixels from the border of the frame

TARGET

HTML attribute

Indicates the frame or window for a document to load into—used with the A, FORM, BASE and AREA tags

NOFRAMES

HTML tag

Indicates HTML code to be displayed in browsers that don't support frames; used in documents containing the FRAMESET container tags

frames

JavaScript property

Array of objects for each frame in a Navigator window

parent

JavaScript property

Indicates the parent frameset document of the currently loaded document

BODY

HTML Tag

Defines the main body of an HTML document

BACKGROUND

HTML attribute

Specifies URL of a background image in the BODY tag

BGCOLOR

HTML attribute

Specifies the background color for a document as a hexadecimal triplet or color name

FGCOLOR

HTML attribute

Specifies the foreground color for a document as a hexadecimal triplet or color name

LINK

HTML attribute

Specifies the color of link text

ALINK

HTML attribute

Specifies the color of active link text

VLINK

HTML attribute

Specifies the color of followed link text

alinkColor

JavaScript property

The color value of active links

anchors

JavaScript property

Array of objects corresponding to each named anchor in a document

bgColor

JavaScript property

The background color value of a document

fgColor

JavaScript property

The foreground color value of a document

forms

JavaScript property

Array of objects corresponding to each form in a document

lastModified

JavaScript property

As a string, the last date the document was modified

linkColor

JavaScript property

The color value of link text

links

JavaScript property

Array of objects corresponding to each link in a document

location

JavaScript property

Object defining the full URL of the document

title

JavaScript property

Title of the document represented as a string

vlinkColor

JavaScript property

The color value of followed links

hash

JavaScript property

An anchor name (location object)

host

JavaScript property

Hostname and port of a URL (location object)

href

JavaScript property

Hostname of a URL (location object)

pathname

JavaScript property

File path from the URL (location object)

port

JavaScript property

Port number from the URL (location object)

protocol

JavaScript property

Protocol part of the URL (location object)

search

JavaScript property

Form data or query from the URL (location object)

assign()

JavaScript method

Sets location.href (location object)

toString()

JavaScript method

Returns location.href as a string (location object)

open()

JavaScript method

Opens a document for a particular MIME type (document object)

close()

JavaScript method

Closes a document for writing (document object)

clear()

JavaScript method

Clears a document window (document object)

self

JavaScript property

Refers to the current window

top

JavaScript property

The top-most parent window

status

JavaScript property

Text displayed in the status bar represented as a string

defaultStatus

JavaScript property

Default text displayed in the status bar

close()

JavaScript method

Closes the window (window object)

open()

JavaScript method

Opens a document in a named window (window object)

setTimeout()

JavaScript method

Pauses for a specified number of milliseconds and then evaluates an expression

clearTimeout()

JavaScript method

Cancels a previously set timeout

onMouseOver

Event handler

Specifies script to execute when the mouse pointer is over a hypertext link

location

JavaScript property

The location of the document currently loaded in a window

Q&A

Q: If I use frames in my documents, will users of any other Web
browsers be able to view my documents.

A: At the present time, only Netscape Navigator 2.0 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.

Q: Is it possible to force changes in the relative size of frames
using JavaScript?

A: No. 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().

Q: Is it possible to set the status bar when the user points at a
form button?

A: No. 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 an 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.10 and Listing 8.11. 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 which 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() 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 selects 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:

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

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

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

    <!-- 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>

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

    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.

  2. This script partially addresses the problem in Listings 8.13 and 8.14—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.

  3. 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>

Previous Page TOC Index Next Page Home