Previous Page TOC Next Page See Page



- 8 -
Frames and JavaScript


Traditionally, Web browsers have been limited to displaying a single HTML document in a single document window.

It simply wasn't possible for Web authors to create sophisticated interfaces involving fixed toolbars, menus, and work areas. Instead, anytime the user requested new information, the complete browser window had to be cleared and updated with a new document.

Starting with Navigator 2, Netscape introduced an extension for HTML known as frames. Frames allow a browser window to be divided into multiple rectangular spaces, or frames, each of which contains separate documents.

Combining frames with JavaScript provides a powerful mechanism for creating sophisticated Web-based interfaces and applications. This chapter shows you how to take advantage of frames and how to work with frames in JavaScript. Specifically, it looks at the following topics:


Frame Basics


Frames enable a browser window to be divided into multiple rectangular panes, each containing its own documents. Links in one document can be made to appear in another frame without requiring a complete update or refresh of all the frames in the window.



Making links display in a different frame is known as targeting. Different links in the same document can be targeted at different frames as well as at new browsers windows.

Using cross-frame links, it's possible to create fixed, permanent mastheads, menu or tool bars, or search forms. Frames also can reduce bandwidth demands by requiring common components such as menus and logos to be loaded once in their own frame (instead of repeatedly with each document request by the user).

Figure 8.1. Frames can be used to create fixed menus and logos, as shown in this example from Landegg Academy in Switzerland (http://www.landegg.org/landegg/).

Tags for Creating Frames


Frames are defined using two new HTML tags: <FRAMESET> and <FRAMES>.

<FRAMESET> is defined in the parent document—the document originally accessed by the user. The <FRAMESET> tag defines how a window should be divided into frames. It can be used to specify rows and columns and their sizes.

The <FRAME> tag is used to indicate which document should be displayed in each of the frames defined in the <FRAMESET> tag.

A third tag, <NOFRAMES>, provides a mechanism for providing text or HTML for users of non-frames capable browsers.



Frameset documents are not restricted to loading into complete windows. The document loaded into an individual frame of a frameset document can be another frameset, which then divides the individual frame according to the specifications of the <FRAMESET> tag. This is explained in more detail later in this chapter, in the section on "Embedded Frames."


The <FRAMESET> Tag

The <FRAMESET> tag defines how to split a window into rows or columns. It takes two possible attributes: ROWS and COLS.

ROWS defines the number of rows into which you may divide the window, and COLS specifies the number of columns into which you can divide the window. The value assigned to rows or columns can either be in pixels or a percentage of the window size into which the frameset is loaded. For instance,

<FRAMESET ROWS="100,*">

creates two frames in rows—the top frame is 100 pixels deep and the second row fills the remainder of the available space. The asterisk is used to indicate the remaining space. If the user resizes the window, the 100 pixel-deep frame retains the same depth, and the size of the lower frame changes to accommodate the new window dimensions.

Relative frame sizes are specified with percentages:

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

This <FRAMESET> tag creates three frames in columns. The outer columns are each 25 percent of the width of the available space and the center frame fills the remaining space.

The ROWS and COLUMNS attributes can be used together to create a grid of rows and columns. For instance, the frameset tag

<FRAMESET ROWS="25%,*" COLS="10%,35%,*">

creates results like those shown in Figure 8.2.

Figure 8.2. Combining ROWS and COLS produces grids.



When combining ROWS and COLS in a <FRAMESET> tag, the order of frames proceeds from the top-left frame to the bottom-right frame, counting across rows and then down columns.


The <FRAME> tag

The <FRAME> tag is used to specify which documents are loaded in each frame of the frameset.

The <FRAME> tag takes six possible attributes, as outlined in Table 8.1.

Table 8.1. Attributes of the <FRAME> tag.

Attribute Description
SRC Specifies the URL of the HTML file to be displayed in a frame.
NAME Specifies the name of a frame for use in targeting and in JavaScript.
NORESIZE Indicates that a frame cannot be resized by the user.
SCROLLING Specifies if scroll bars should be displayed. Possible values are YES, NO and AUTO.
MARGINHEIGHT Indicates the width of the top and bottom margins in pixels.
MARGINWIDTH Indicates the width of the left and right margins in pixels.

For instance, the following code loads document1.html in frame1 and document2.html in frame2.

<FRAMESET ROWS="150,*">
   <FRAME SRC="document1.html" NAME="frame1" SCROLLING="NO">
   <FRAME SRC="document2.html" NAME="frame2" NORESIZE>
</FRAMESET>

The NORESIZE tag indicates that the size of the frames is fixed and, because there are only two frames, both frames are effectively of fixed size. The SCROLLING="NO" attribute means the top frame won't have scrollbars even if the document doesn't fit in the frame. The AUTO value of the SCROLLING attribute places scrollbars in a frame only if the document doesn't fit. By default, scrollbars appear when necessary.

The <NOFRAMES> Tag

The <NOFRAMES> tag is a mechanism for providing non-frame content to browsers that don't support Netscape's frame extensions.

Any content between <NOFRAMES> and </NOFRAMES> is ignored by Navigator and other frames-capable browsers, but is evaluated and displayed by other browsers.

For example, in the following HTML code, users with other browsers are told they need to use Navigator to view the site.

<FRAMESET ROWS="25%,*">
   <FRAME SRC="document1.html">
   <FRAME SRC="document2.html">
</FRAMESET>
<NOFRAMES>
   This site uses frames. Please download
   Netscape Navigator 2 or 3 or another
   frames-capable browser.
</NOFRAMES>

This produces results like those shown in Figure 8.3.

Figure 8.3. Using the <NOFRAMES>, special content for users of other browsers can be displayed.

Embedded Framesets


With simple frameset structures, frame layouts are limited to rows, columns, or simple grids. However, many sites use more complicated page layouts like the ones shown in Figure 8.4.

Figure 8.4. Embedded framesets can be used to create complex frame layouts.

Complex embedded framesets are achieved by using embedded framesets. The principle behind embedding framesets is that it is possible for a document loaded into any frame to itself be a frameset document. This embedded frameset then uses the space available to it in its assigned frame to display the layout defined in its <FRAMESET> tag.

Embedded framesets can either be loaded from separate HTML files using the <FRAME> tag in the parent frameset document, or the child <FRAMESET> definition can actually be in the same document as the parent <FRAMESET>.

For instance, if you have a parent frameset document

<FRAMESET ROWS="50%,*">
   <FRAME SRC="document1.html">
   <FRAME SRC="document2.html" NAME="frame2">
</FRAMESET>

where document1.html contains another frameset

<FRAMESET COLS="50%,*">
   <FRAME SRC="document3.html" NAME="frame3">
   <FRAME SRC="document4.html" NAME="frame4">
</FRAMESET>

then the result would be two rows where the top frame is further divided into two columns, as shown in Figure 8.5.

Figure 8.5. Embedded framesets can be created in multiple files or a single file.

The embedded framesets created above with two files also can be created in one frameset file by replacing the first <FRAME> tag with the embedded frameset

<FRAMESET ROWS="50%,*">
   <FRAMESET COLS="50%,*">
      <FRAME SRC="document3.html" NAME="frame3">
      <FRAME SRC="document4.html" NAME="frame4">
   </FRAMESET>
   <FRAME SRC="document2.html" NAME="frame2">
</FRAMESET>

Targeting Frames


One main purpose behind naming frames with the NAME attribute of the <FRAME> tag is to target links.

For instance, in the preceding example, links in document2.html could be targeted at frame 4 by adding the attribute TARGET to the <A> tag:

<A HREF="link1.html" TARGET="frame4">

As mentioned in Chapter 7, "Working with Forms," when the <FORM> tag was discussed, the TARGET attribute can be used in the <FORM> tag to target the results of a form submission to another frame:

<FORM METHOD=method ACTIOn="Script_URL" TARGET="frame4">

For documents with numerous links all targeted at the same frame, the <BASE> tag can be used to set a global target for a whole document. The <BASE> tag usually appears in the header of an HTML document and takes the form

<BASE TARGET="targetName">

The global target defined in the <BASE> tag can be overridden by a TARGET attribute in an <A> or a <FROM> tag elsewhere in the same document.

Targeting is not limited to named frames. Netscape provides four special names that can be used as targets: _blank, _self, _parent, and _top.

_blank causes the link or form submission result to open in a new, unnamed window. _self causes links to load in the same frame in which the link was clicked, _parent causes links or form results to appear in the immediate parent frameset, and _top causes links to load in the full browser window.

The frame Object


The frame object in JavaScript reflects frames defined in an HTML file. Frame objects are stored in the frames array, which is a property of the window object. In this way, each frame in a window is accessible by index number in the array. Index numbers start at zero. Objects are stored in the array in the order of their specification in the frameset document.

By using the special keyword parent, it is possible to reference any other frame in a window, using the frame's index number. For instance, in the frameset

<FRAMESET ROWS="50%,*">
   <FRAME SRC="document1.html">
   <FRAME SRC="document2.html" NAME="frame2">
</FRAMESET>

a script in document1.html could refer to frame2 with

parent.frames[1]

Similarly, the first frame is

parent.frames[0].

Frame names also can be used to refer to the specific named frames. In this example, the second frame could be referenced with

parent.frame2

In the case of embedded framesets, things are a little more complicated. In the preceding four-document example, where the first frameset document looked like

<FRAMESET ROWS="50%,*">
   <FRAME SRC="document1.html">
   <FRAME SRC="document2.html" NAME="frame2">
</FRAMESET>

and document1.html was another frameset defined by

<FRAMESET COLS="50%,*">
   <FRAME SRC="document3.html" NAME="frame3">
   <FRAME SRC="document4.html" NAME="frame4">
</FRAMESET>

the simple use of parent.frameName or parent.frames[index] can't always be maintained.

For instance, for a script in frame4 to access frame2 it is not sufficient to simply use parent.frames[1], because the immediate parent frameset of frame4 is frame1 and within that frameset frames[1] refers to frame4. Instead, the following form,

parent.parent.frames[1]

would be used to refer to the second frame in the parent of the immediate parent frameset.



In some cases it is possible to eliminate the use of multiple parent references to get to the desired object. Using the special keyword top, it is possible to refer to the top frameset in the current window. In this way, the preceding example could use top.frames[1] to refer to frame2.

The frames array has one property—length—which is an integer indicating the number of frame objects stored in the frames array. Effectively, this number is the number of child frames within a frameset.

Properties of the frame Object


The frame object has a number of properties available for working directly with the frame itself in relation to the frameset. This is different from working with documents in the frame or the frame's history list.

The properties of the frame object follow:

For instance, in the preceding embedded frameset example, a script in frame1 could use self.length to return a value of zero and parent.frames[1].name to return the string "frame2."

In addition, each frame object has a document object, a location object, and a history object as properties. This is tied to the fact that each frame has a document loaded into it, and a separate history list distinct from the history list for the window.

Methods of the frame Object


The frame object has two methods: clearTimeout() and setTimout().

Both of these methods are related to the timing of events. Together they provide a simple method for scheduling actions and, if necessary, canceling those actions before they occur.

The setTimeout() method enables an expression to be evaluated after a specified delay in milliseconds. The basic syntax of a setTimout() command is

timeOut = setTimout("expression",delay);

While timeOut is a name assigned to the specific scheduled event, expression is a string or property to be evaluated, and delay is an integer indicating the number of milliseconds to wait before evaluating expression.

For instance, the following code would wait 2 seconds (2000 milliseconds) before executing the alert() method specified in the expression portion of the command.

delayAlert = setTimeout("alert('You made it')",2000);

Being able to name each time-out means it is possible to use the clearTimeout() method to cancel a scheduled time-out before its expression is evaluated. Using the syntax

clearTimeout(timeOut);

where timeOut is the name of the timeOut (such as delayAlert in the alert() preceding example), it is possible to cancel a time-out that hasn't occurred.



Specifying a name for a time-out is not required. It is optional. If no name is specified, it isn't possible to clear the time-out with clearTimeout().

To illustrate the use of the setTimeout() method, the following document can be used to load a random string into a frame which is at a set interval:

<HEAD>
<TITLE>setTimeout() Example</TITLE>
<SCRIPT LANGUAGE="JavaScript">
var delay = 5000; var choices = new Array(5);
choices[0] = "This is Choice 1";
choices[1] = "This is Choice 2";
choices[2] = "This is Choice 3";
choices[3] = "This is Choice 4";
choices[4] = "This is Choice 5";
function displayRandom() {
     document.writeln(choices[Math.floor(5 * Math.random(choices.length))]);
}
</SCRIPT>
</HEAD>
<BODY onLoad="self.setTimeout('self.location = self.location',delay)">
<SCRIPT LANGUAGE="JavaScript">
displayRandom();
</SCRIPT>
</BODY>

This produces results similar to those shown in Figure 8.6.

Figure 8.6. Using setTimeout(), it is possible to schedule events, such as displaying random text at specified intervals.

In this example, two simple principles are used. This first is that a script can output text to the document window. The function displayRandom() uses the Math.random() method, discussed earlier when at Math object was examined, to randomly select one of the five strings stored in the choices array, then simply writes it to the frame using document.writeln().

The onLoad event handler in the <BODY> tag handles the scheduling of a reload. The setTimout() tag uses the delay variable to specify the length of the delay (in this case 5000 milliseconds, or five seconds) before evaluating the expression self.location = self.location, which simply reloads the current document into the window.

Managing Cross-Frame Communication


With frames, it is possible to make use of functions and variables stored in other frames. You also can access the properties and methods of objects associated with the documents in other frames.

For example, the following frameset produces a simple calculator in which one frame has all the buttons for keying in numbers and functions and the other window has what amounts to a paper tape record of all entries into the calculator.

The parent frameset looks like the following:

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

The source code for the file control.html follows:

Listing 8.1. A calculator using frames.

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
function nextNumber(number) {
   parent.output.document.displayForm.currentValue.value += number;
   parent.output.document.displayForm.runningTape.value += number;
}
function doAction(action) {
   var toDo = parent.output.document.displayForm.total.value +
              parent.output.document.displayForm.currentAction.value +
              parent.output.document.displayForm.currentValue.value;
   parent.output.document.displayForm.currentValue.value = "";
   parent.output.document.displayForm.currentAction.value = action;
   parent.output.document.displayForm.runningTape.value += action + " \n";
   parent.output.document.displayForm.total.value = eval(toDo);
}
function doEqual() {
   doAction("=");
   parent.output.document.displayForm.runningTape.value += "----------\n";
   parent.output.document.displayForm.runningTape.value += parent.output.document.displayForm.total.value + "\n";
   parent.output.document.displayForm.runningTape.value += "----------\n";
   parent.output.document.displayForm.total.value = "0";
   parent.output.document.displayForm.currentValue.value = "";
   parent.output.document.displayForm.currentAction.value = "+";
}
</SCRIPT>
</HEAD>
<BODY>
<FORM>
<TABLE>
<TR>
<TD>
<INPUT TYPE=button NAME="7" VALUE="7" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="8" VALUE="8" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="9" VALUE="9" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="+" VALUE="+" onClick="doAction(this.value)">
</TD>
</TR>
<TR>
<TD>
<INPUT TYPE=button NAME="4" VALUE="4" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="5" VALUE="5" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="6" VALUE="6" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="-" VALUE="-" onClick="doAction(this.value)">
</TD>
</TR>
<TR>
<TD>
<INPUT TYPE=button NAME="1" VALUE="1" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="2" VALUE="2" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="3" VALUE="3" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="*" VALUE="*" onClick="doAction(this.value)">
</TD>
</TR>
<TR>
<TD>
<INPUT TYPE=button NAME="0" VALUE="0" onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="." VALUE="." onClick="nextNumber(this.value)">
</TD>
<TD>
<INPUT TYPE=button NAME="=" VALUE="=" onClick="doEqual()">
</TD>
<TD>
<INPUT TYPE=button NAME="/" VALUE="/" onClick="doAction(this.value)">
</TD>
</TR>
</TABLE>
</FORM>
</BODY>

The file output.html looks like:

<BODY>
<FORM NAME="displayForm">
<INPUT TYPE=hidden NAME="currentValue" VALUE="">
<INPUT TYPE=hidden NAME="currentAction" VALUE="+">
<INPUT TYPE=hidden NAME="total" VALUE="0" SIZE=6>
<TEXTAREA NAME="runningTape" ROWS=30 COLS=10></TEXTAREA><BR>
</BODY>

Figure 8.7. Using cross-frame communication, the calculator buttons on the left update the textarea field in the frame on the right.

This example demonstrates the basics of cross-frame communication. Although all the logic in the program is in the left-hand frame (control.html), the output occurs in the right-hand frame. In addition, all data for the calculator is stored in hidden fields in the form in the right-hand frame (output.html).

In order to achieve this, the script in control.html references the values stored and displayed in the output form with the following structure

parent.output.displayForm.fieldName.value

The program itself breaks down into three simple functions: nextNumber(), doAction(), and doEqual(). nextNumber() displays numbers entered by the user and adds it to the number stored in currentValue. doAction() handles processing when a user clicks on one of the four mathematical functions. doAction() performs the previously entered action (stored in currentAction) on the value stored in currentValue and the value stored in total. It then updates display and total and stores the new action in currentAction.

Finally, doEqual() completes the last action remaining to be performed, outputs the result and clears total, currentValue,and currentAction to their default state in preparation for a new calculation.

It is clear that with a large number of embedded framesets, handling cross frame communication can become troublesome, requiring cryptic references to

parent.parent.parent.frames[3].frameName.propertyName

or

parent.parent.frames[2].frames[5].methodName()

The best way to handling this is to use a frameset manager—generic JavaScript code that handles cross-frame function calls and, possibly, access to properties and variables across functions.



Bill Dortch, well known in the JavaScript world for developing various toolsets to help JavaScript designers (and for running hIdaho Design) has written a publicly-available frameset manager called the hIdaho Frameset, which is available on the Web at http://www.hidaho.com/frameset/.

To illustrate how a frameset manager works, the following script is a simple frameset manager that should be included the top-most frameset in a series of embedded framesets:

<SCRIPT LANGUAGE="JavaScript">
var functions = new Array(0);
function registerFunction(objectName,functionName) {
 functions[functionName] = objectName;
}
function callFunction(functionName) {
 var args = callFunction.arguments;
 var argString = "";
 var argList = new Array (args.length);
 for (i = 1; i < args.length; i++) {
  argList[i-1] = args[i];
 }
 for (i = 0; i < argList.length; i++) {
  argString += "argList[" + i + "],";
 }
 argString += "argList[" + argList.length + "]";
 return eval("self." + functions[functionName] + "(" + argString + ")");
}
function alreadyRegistered(functionName) {
 return (functions[functionName] != null);
}
</SCRIPT>

In addition, the manager requires the following function in each child frameset:

function registerFunction(objectName,functionName) {
        parent.registerFunction(self.name + "." + objectName,functionName);
}

The following function is included in each document displayed in a frame that contains functions— which need to be registered with the frameset manager:

function registerFunction(functionName) {
        parent.registerFunction(self.name + "." + functionName,functionName);
}

The way the frameset manager works is relatively simple: In documents containing functions to register, a call to registerFunction() needs to be made with one argument—the function name.

The registerFunction() function then calls registerFunction() in its immediate parent frameset and passes two arguments: the name of the function with the frame name as a prefix, and the function name itself. In intermediate framesets, registerFunction() adds its frame name to the first argument and calls registerFunction() in the next parent frameset in the hierarchy.

In the top-level frameset, registerFunction() receives the two arguments passed up throughout the child framesets: the complete name of the function, including the frame names needed to call the function from the top-level frameset, and the simple function name itself.

The function then adds the complete function path and name as an entry in the functions array, using the simple function name as the array index.

The frameset manager includes two other functions: callFunction() and alreadyRegistered(). alreadyRegistered() simply checks if an entry is in the array functions and returns true or false on the basis of that check.

callFunction() does a bit more. It receives two arguments: the name of the function, and arguments to be passed to the function. It then builds a string representing the function call and evaluates it using eval() before returning the result.

To demonstrate how the frameset manager works, the following example divides the window into three frames: A button in the right-hand frame calls a function in the bottom-left frame, which displays an alert dialog box.

The parent frameset looks like

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
var functions = new Array(0);
function registerFunction(objectName,functionName) {
 functions[functionName] = objectName;
}
function callFunction(functionName) {
 var args = callFunction.arguments;
 var argString = "";
 var argList = new Array (args.length);
 for (i = 1; i < args.length; i++) {
  argList[i-1] = args[i];
 }
 for (i = 0; i < argList.length; i++) {
  argString += "argList[" + i + "],";
 }
 argString += "argList[" + argList.length + "]";
 return eval("self." + functions[functionName] + "(" + argString + ")");
}
function alreadyRegistered(functionName) {
 return (functions[functionName] != null);
}
</SCRIPT>
</HEAD>
<FRAMESET COLS="50%,*">
        <FRAME SRC="left.html" NAME="left">
        <FRAME SRC="right.html" NAME="right">
</FRAMESET>

The file left.html is another frameset file. Notice that the file contains the registerFunction() function for intermediate framesets:

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
function registerFunction(objectName,functionName) {
        parent.registerFunction(self.name + "." + objectName,functionName);
}
</SCRIPT>
</HEAD>
<FRAMESET ROWS="50%,*">
        <FRAME SRC="top.html" NAME="top">
        <FRAME SRC="bottom.html" NAME="bottom">
</FRAMESET>

bottom.html holds the function that will be registered with the manager. It includes the registerFunction() function for document files and registers the function doAlert() by calling it:

<HEAD>
<SCRIPT LANGUAGE="JavaScript">
function registerFunction(functionName) {
        parent.registerFunction(self.name + "." + functionName,functionName);
}
registerFunction("doAlert");
function doAlert(toDisplay) {
        alert(toDisplay);
}
</SCRIPT>
</HEAD>
<BODY>
<CENTER>The function is contained in this file.</CENTER>
</BODY>

right.html contains the button that triggers a cross-frame call after the alreadyRegistered() function has been called to confirm that doAlert() is loaded and registered:

<BODY>
<FORM>
<H3>Call a function in another frame:</H3>
<INPUT TYPE=button VALUE="PUSH ME"
onClick="if (top.alreadyRegistered('doAlert')) { top.callFunction('doAlert','This was a Cross-frame Alert.') }">
</FORM>
</BODY>

The file top.html is not functional: it just displays text. The results look like those shown in Figure 8.8.

Figure 8.8. Using a frameset manager makes it easier to make cross-function calls using embedded framesets.

Summary


This chapter covered frames and techniques for working with them. The concept of embedded framesets was discussed in detail, and a basic frameset manager was outlined to ease the management of function calls across frames in embedded framesets.

In Chapter 9, Cookies—a facility for maintaining client-side state information—is discussed.

Previous Page Page Top TOC Next Page See Page