
JavaScript: Windows and Frames
Pages: 1, 2, 3, 4, 5, 6, 7
Most of the client-side JavaScript examples we've seen so far
have involved only a single window or frame. In the real world, JavaScript
applications often involve multiple windows or frames. Recall that frames
within a window are represented by Window objects; JavaScript makes little
distinction between windows and frames. In the most interesting applications,
there is JavaScript code that runs independently in each of several windows.
The next section explains how the JavaScript code in each window can interact
and cooperate with each of the other windows and with the scripts running in
each of those windows.
Relationships Between Frames
We've already seen that the open( )
method of the Window object returns a new Window object representing the newly
created window. We've also seen that this new window has an
opener property that refers back to the original window.
In this way, the two windows can refer to each other, and each can read
properties and invoke methods of the other. The same thing is possible with
frames. Any frame in a window can refer to any other frame through the use of
the frames, parent, and
top properties of the Window object.
Every window has a frames property.
This property refers to an array of Window objects, each of which represents a
frame contained within the window. (If a window does not have any frames, the
frames[] array is empty and
frames.length is zero.) Thus, a window (or frame) can
refer to its first subframe as frames[0], its
second subframe as frames[1], and so on. Similarly,
JavaScript code running in a window can refer to the third subframe of its
second frame like this:
frames[1].frames[2]
Every window also has a parent
property, which refers to the Window object in which it is contained. Thus,
the first frame within a window might refer to its sibling frame (the second
frame within the window) like this:
parent.frames[1]
If a window is a top-level window and not a frame,
parent simply refers to the window itself:
parent == self; // For any top-level window
If a frame is contained within another frame that is contained
within a top-level window, that frame can refer to the top-level window as
parent.parent. The top
property is a general-case shortcut, however: no matter how deeply a frame is
nested, its top property refers to the top-level
containing window. If a Window object represents a top-level window,
top simply refers to that window itself. For frames that
are direct children of a top-level window, the top
property is the same as the parent property.
Frames are typically created with
<frameset> and
<frame> tags. In HTML 4, however, as implemented in
IE 4 and later and Netscape 6 and later, the
<iframe> tag can also be used to create an "inline
frame" within a document. As far as JavaScript is concerned, frames created
with <iframe> are the same as frames created
with <frameset> and
<frame>. Everything discussed here applies to both
kinds of frames.
Figure
13-4 illustrates these relationships between frames and shows how code
running in any one frame can refer to any other frame through the use of the
frames, parent, and
top properties. The figure shows a browser window
that contains two frames, one on top of the other. The second frame (the
larger one on the bottom) itself contains three subframes, side by side.

Figure 13-4. Relationships between frames
|
With this understanding of the relationships between frames, you
may want to revisit Example
13-6, paying particular attention this time to the way the code (which is
written to run in a second frame) refers to the
history and location
properties of the first frame.
Window and Frame Names
The second, optional argument to the open(
) method discussed earlier is a name for the newly created window. When
you create a frame with the HTML <frame> tag,
you can specify a name with the name attribute. An
important reason to specify names for windows and frames is that those names
can be used as the value of the target attribute of
the <a>,
<map>, and <form>
tags. This value tells the browser where you want to display the results of
activating a link, clicking on an image map, or submitting a form.
For example, if you have two windows, one named
table_of_contents and the other
mainwin, you might have HTML like the following in the
table_of_contents window:
<a href="chapter01.html" target="mainwin">
Chapter 1, Introduction
</a>
The browser loads the specified URL when the user clicks on this
hyperlink, but instead of displaying the URL in the same window as the link,
it displays it in the window named mainwin. If
there is no window with the name mainwin, clicking
the link creates a new window with that name and loads the specified URL into
it.
The target and
name attributes are part of HTML and operate without the
intervention of JavaScript, but there are also JavaScript-related reasons to
give names to your frames. We've seen that every Window object has a
frames[] array that contains references to each of its
frames. This array contains all the frames in a window (or frame), whether or
not they have names. If a frame is given a name, however, a reference to that
frame is also stored in a new property of the parent Window object. The name
of that new property is the same as the name of the frame. Therefore, you
might create a frame with HTML like this:
<frame name="table_of_contents" src="toc.html">
Now you can refer to that frame from another, sibling frame
with:
parent.table_of_contents
This makes your code easier to read and understand than using
(and relying on) a hardcoded array index, as you'd have to do with an unnamed
frame:
parent.frames[1]
The name property of any Window
object contains the name of that window. In JavaScript 1.0, this property is
read-only. In JavaScript 1.1 and later, however, you can set this property,
thereby changing the name of a window or a frame. One common reason to do this
is to set the name of the initial browser window. When a browser starts up,
the initial window has no name, so it cannot be used with the
target attribute. If you set the
name property of the window, however, you can then use
that name in target attributes.
JavaScript in Interacting Windows
Recall what we learned in Chapter 12: the Window object serves
as the global object for client-side JavaScript code, and the window serves as
the execution context for all JavaScript code it contains. This holds true for
frames as well: every frame is an independent JavaScript execution context.
Because every Window object is its own global object, each window defines its
own namespace and its own set of global variables. When viewed from the
perspective of multiple frames or windows, global variables do not seem all
that global, after all!
Although each window and frame defines an independent JavaScript
execution context, this does not mean that JavaScript code running in one
window is isolated from code running in other windows. Code running in one
frame has a different Window object at the top of its scope chain than code
running in another frame. However, the code from both frames is executed by
the same JavaScript interpreter, in the same JavaScript environment. As we've
seen, one frame can refer to any other frame using the
frames, parent, and
top properties. So, although JavaScript code in different
frames is executed with different scope chains, the code in one frame can
still refer to and use the variables and functions defined by code in another
frame.
For example, suppose code in frame A defines a variable
i:
var i = 3;
That variable is nothing more than a property of the global
object--a property of the Window object. Code in frame A could refer to the
variable explicitly as such a property with either of these two expressions:
window.i
self.i
Now suppose that frame A has a sibling frame B that wants to set
the value of the variable i defined by the code in
frame A. If frame B just sets a variable i, it
merely succeeds in creating a new property of its own Window object. So
instead, it must explicitly refer to the property i
in its sibling frame with code like this:
parent.frames[0].i = 4;
Recall that the function keyword that
defines functions declares a variable just like the
var keyword does. If JavaScript code in frame A declares
a function f, that function is defined only within
frame A. Code in frame A can invoke f like this:
f( );
Code in frame B, however, must refer to
f as a property of the Window object of frame A:
parent.frames[0].f( );
If the code in frame B needs to use this function frequently, it
might assign the function to a variable of frame B so that it can more
conveniently refer to the function:
var f = parent.frames[0].f;
Now code in frame B can invoke the function as
f( ), just as code in frame A does.
When you share functions between frames or windows like this, it
is very important to keep the rules of lexical scoping in mind. A function is
executed in the scope in which it was defined, not in the scope from which it
is invoked. Thus, to continue with the previous example, if the function
f refers to global variables, these variables are looked
up as properties of frame A, even when the function is invoked from frame
B.
If you don't pay careful attention to this, you can end up with
programs that behave in unexpected and confusing ways. For example, suppose
you define the following function in the
<head> section of a multiframe document, with the
idea that it will help with debugging:
function debug(msg) {
alert("Debugging message from frame: " + name + "\n" + msg);
}
The JavaScript code in each of your frames can refer to this
function as top.debug( ). Whenever this function is
invoked, however, it looks up the variable name in
the context of the top-level window in which the function is defined, rather
than the context of the frame from which it is invoked. Thus, the debugging
messages always carry the name of the top-level window, rather than the name
of the frame that sent the message, as was intended.
Remember that constructors are also functions, so when you
define a class of objects with a constructor function and an associated
prototype object, that class is defined only for a single window. Recall the
Complex class we defined in Chapter 8, and consider the following multiframed
HTML document:
<head>
<script src="Complex.js"></script>
</head>
<frameset rows="50%,50%">
<frame name="frame1" src="frame1.html">
<frame name="frame2" src="frame2.html">
</frameset>
JavaScript code in the files frame1.html
and frame2.html cannot create a Complex object with an
expression like this:
var c = new Complex(1,2); // Won't work from either frame
Instead, code in these files must explicitly refer to the
constructor function:
var c = new top.Complex(3,4);
Alternatively, code in either frame can define its own variable
to refer more conveniently to the constructor function:
var Complex = top.Complex;
var c = new Complex(1,2);
Unlike user-defined constructors, predefined constructors are
automatically predefined in all windows. Note, however, that each window has
an independent copy of the constructor and an independent copy of the
constructor's prototype object. For example, each window has its own copy of
the String( ) constructor and the
String.prototype object. So, if you write a new method
for manipulating JavaScript strings and then make it a method of the String
class by assigning it to the String.prototype
object in the current window, all strings in that window can use the new
method. However, the new method is not accessible to strings defined in other
windows. Note that it does not matter which window holds a reference to the
string; only the window in which the string was actually created matters.
Example: Colored Frames
Example
13-7, a frame set that defines a grid of nine frames, demonstrates some of
the techniques we've discussed in this chapter. The
<head> section of the frame set includes a
<script> that defines a JavaScript function named
setcolor( ). The onload
event handler of the <frameset> tag invokes
setcolor( ) once for each of the nine frames.
setcolor( ) is passed a Window object
as its argument. It generates a random color and uses it with the
Document.write( ) method to create a new document that is
empty except for a background color. Finally, setcolor(
) uses the setTimeout( ) method to schedule
itself to be called again in one second. This call to
setTimeout( ) is the most interesting part of the
example. Notice especially how it uses the parent
and name properties of Window objects.
Example 13-7: A frame color animation
<head>
<title>Colored Frames</title>
<script>
function setcolor(w) {
// Generate a random color
var r = Math.floor((Math.random( ) * 256)).toString(16);
var g = Math.floor((Math.random( ) * 256)).toString(16);
var b = Math.floor((Math.random( ) * 256)).toString(16);
var colorString = "#" + r + g + b;
// Set the frame background to the random color
w.document.write("<body bgcolor='" + colorString
+ "'></body>");
w.document.close( );
// Schedule another call to this method in one second.
// Since we call the setTimeout( ) method of the frame,
// the string will be executed in that context, so we
// must prefix properties of the top-level window with
// "parent.".
w.setTimeout('parent.setcolor(parent.' + w.name
+ ')', 1000);
// We could also have done the same thing more simply
// like this: setTimeout('setcolor(' + w.name + ')', 1000);
}
</script>
</head>
<frameset rows="33%,33%,34%" cols="33%,33%,34%"
onload="for(var i = 0; i < 9; i++) setcolor(frames[i]);">
<frame name="f1" src="javascript:''">
<frame name="f2" src="javascript:''">
<frame name="f3" src="javascript:''">
<frame name="f4" src="javascript:''">
<frame name="f5" src="javascript:''">
<frame name="f6" src="javascript:''">
<frame name="f7" src="javascript:''">
<frame name="f8" src="javascript:''">
<frame name="f9" src="javascript:''">
</frameset>
View more information about JavaScript: The Definitive Guide, 4th Edition
Return to the JavaScript and CSS DevCenter.


|