A common misunderstanding among Web developers is the relationship between frames and windows. In reality, both from the perspective of (X)HTML and JavaScript, each frame shown on screen is a window that can be manipulated. In fact, when a browser window contains multiple frames, it is possible to access each of the separate window objects through window.frames[], which is an array of the individual frames in the window. The basic properties useful for manipulating frames are detailed in Table 12-4. Notice how many of them are related to the reserved frame values used in (X)HTML.
Window Property |
Description |
---|---|
frames[] |
An array of all the frame objects contained by the current window. |
length |
The number of frames in the window. Should be the same value as window.frames.length. |
name |
The current name of the window. This is both readable and settable since JavaScript 1.1. |
parent |
A reference to the parent window. |
self |
A reference to the current window. |
top |
A reference to the top window. Often the top and the parent will be one and the same unless the <frame> tag loads documents containing more frames. |
window |
Another reference to the current window. |
The major challenge using frames and JavaScript is to keep the names and relationships between frames clear so that references between frames are formed correctly. Consider if you have a document called frames.html with the following markup.
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>FrameSet Test<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<frameset rows="33%,*,33%">> <<frame src="framerelationship.html" name="frame1" id="frame1" />> <<frame src="moreframes.html" name="frame2" id="frame2" />> <<frame src="framerelationship.html" name="frame5" id="frame5" />> <</frameset>> <</html>>
In this case, the window containing this document is considered the parent of the three frames (frame1,frame2, and frame5). While you might expect to use a value like
window.frames.length
to determine the number of frames in the window, you will actually probably have to run the script from within a child frame. Thus, you would actually use
window.parent.frames.length
or just
parent.frames.length
The parent property allows a window to determine the parent window. We could also use the top property that provides us a handle to the top window that contains all others. This would be written top.frames.length. You do need to be careful, though; unless you have nested frames, the parent and top may actually be one and the same.
To access a particular frame, we can use both its name and its position in the array, so
parent.frames[0].name
would print out the name of the first frame, which in our case is frame1. We could also access the frame from another child frame using parent.frame1 or even parent.frames["frame1"] using the associate array aspect of an object collection. Remember a frame contains a window, so once you have this, you can then use all the Window and Document methods on what it contains.
The next example shows the idea of frame names and the way they are related to each other. There are three files that are required for this example, two framesets (frames.html and moreframes.html), and a document (framerelationship.html) that contains a script that prints out the self, parent, and top relationships of frames.
File: frames.html
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>FrameSet Test<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<frameset rows="33%,*,33%">> <<frame src="framerelationship.html" name="frame1" id="frame1" />> <<frame src="moreframes.html" name="frame2" id="frame2" />> <<frame src="framerelationship.html" name="frame5" id="frame5" />> <</frameset>> <</html>>
File: moreframes.html
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">> <<head>> <<title>>More Frames<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<frameset cols="50%,50%">> <<frame src="framerelationship.html" name="frame3" id="frame3" />> <<frame src="framerelationship.html" name="frame4" id="frame4" />> <</frameset>> <</html>>
File: framerelationship.html
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>Frame Relationship Viewer<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<body>> <<script type="text/javascript">> <<!-- var msg=""; var i = 0; msg += "<<h2>>Window: "+ window.name + "<</h2>><<hr />>"; if (self.frames.length >> 0) { msg += "self.frames.length = " + self.frames.length + "<<br />>" for (i=0; i << self.frames.length; i++) msg += "self.frames["+i+"].name = "+ self.frames[i].name + "<<br />>"; } else msg += "Current window has no frames directly within it<<br />>"; msg+="<<br />>"; if (parent.frames.length >> 0) { msg += "parent.frames.length = " + parent.frames.length + "<<br />>" for (i=0; i << parent.frames.length; i++) msg += "parent.frames["+i+"].name = "+ parent.frames[i].name + "<<br />>"; } msg+="<<br />>"; if (top.frames.length >> 0) { msg += "top.frames.length = " + top.frames.length + "<<br />>" for (i=0; i << top.frames.length; i++) msg += "top.frames["+i+"].name = "+ top.frames[i].name + "<<br />>"; } document.write(msg); // -->> <</script>> <</body>> <</html>>
The relationships using these example files are shown in Figure 12-2.
Once you understand the relationships between frames, you will find it much easier to assign variables to particular frames within deeper pages rather than using the parent.frames[] array all the time. For example, given a simple frameset like this,
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>Two Frames<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<frameset cols="300,*">> <<frame src="navigation.html" name="frame1" id="frame1" />> <<frame src="content.html" name="frame2" id="frame2" />> <</frameset>> <</html>>
within the navigation window, you might set a variable to reference the content frame like so:
var contentFrame = parent.frames[1]; // or reference by name
This way you could just reference things by contentFrame rather than the long array path.
One variation of frames that deserves special attention is the <<iframe>> or inline frame. The idea with an inline frame is that you can add a frame directly into a document without using a frameset. For example,
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>Iframe<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<body>> <<h1>>Regular Content here<</h1>> <<iframe src="http://www.google.com" name="iframe1" id="iframe1" height="200" width="200">><</iframe>> <<h1>>More content here<</h1>> <</body>> <</html>>
produces a page something like this:
The question that then begs is this: how do we control this type of frame? In reality, it is much easier since it is within the frames[] array of the current window. Furthermore, if the inline frame is named, you can use DOM methods like getElementById to access the object. The simple example here demonstrates this idea.
<<iframe src="http://www.google.com" name="iframe1" id="iframe1" height="200" width="200">><</iframe>> <<form action="#" method="get">> <<input type="button" value="Load by Frames Array" onclick="frames['iframe1'].location='http://www.javascriptref.com';" />> <<input type="button" value="Load by DOM" onclick="document.getElementById('iframe1').src='http://www.pint.com';" />> <</form>>
While inline frames seem to be a simplification of standard frames, they are far more interesting than these examples suggest. In fact, we’ll see in Chapter 20 that <<iframe>>s serve as one of the primary methods to use JavaScript to communicate with a Web server. For now, though, we put off this advanced application and study some more common JavaScript-frame applications.
Now that we are familiar with frame naming conventions, it is time to do something with them. In this section we present some solutions for common frame problems and hint at the larger issues with frame usage.
A common question developers have with HTML is how to load multiple frames with a link. (X)HTML provides the target attribute to target a single frame, for example, framename, like so:
<<a href="/" target="framename">>Google<</a>>
However, how would you target two or more frames with a single link click? The answer, of course, is by using JavaScript. Consider the frameset here:
<<frameset cols="300,* ">> <<frame src="navigation.html" name="frame1" id="frame1" />> <<frame src="content.html" name="frame2" id="frame2" />> <<frame src="morecontent.html" name="frame3" id="frame3" />> <</frameset>>
In this case, we want a link in the navigation.html file to load two windows at once. We could write a simple set of JavaScript statements to do this, like so:
<<a href="javascript: parent.frames['frame2'].location='http://www.google.com'; parent.frames['frame3'].location='http://www.javascriptref.com'; ">>Two Sites<</a>>
This approach can get somewhat unwieldy, so you might instead want to write a function called loadFrames() that does the work. You might even consider using a generic function that takes two arrays, one with frames and one with URL targets, and loads each one by one, as demonstrated here:
<<script type="text/javascript">> <<!-- function loadFrames(theFrames,theURLs) { if ( (loadFrames.arguments.length != 2) || (theFrames.length != theURLs.length) ) return for (var i=0;i<<theFrames.length;i++) theFrames[i].location = theURLs[i]; } //-->> <</script>> <<a href="javascript:loadFrames([parent.frames['frame2'],parent.frames['frame3'], parent.frames['frame4']],['http://www.google.com','http://www.javascriptref.com', 'http://www.ucsd.edu']);">>Three Sites<</a>>
While frames can be very useful, particularly for state management in JavaScript, they also can cause Web designers significant problems. For example, some sites will put frames around all outbound links, taking away valuable screen real estate. Often site designers will employ a technique called “frame busting” to destroy any enclosing frameset their page may be enclosed within. This is very easy using the following script that sets the topmost frame’s current location to the value of the page that should not be framed.
<<script type="text/javascript">> <<!-- function frameBuster() { if (window != top) top.location.href = location.href; } window.onload = frameBuster; // -->> <</script>>
The converse problem to the one solved by frame busting would be to avoid framed windows from being displayed outside of their framing context. This occasionally happens when users bookmark a piece of a frameset or launch a link from a frameset into a new window. The basic idea would be to have all framed documents look to make sure they are inside of frames by looking at each window’s location object, and if not, to dynamically rebuild the frameset document. For example, given a simple two-frame layout like in a file frameset.html,
<<frameset cols="250,*">> <<frame src="navigation.html" name="navigation" id="navigation" />> <<frame src="content.html" name="content" id="content" />> <</frameset>>
you might be worried that a user could bookmark or enter directly the navigation.html or content.html URL. To rebuild the frameset in navigation.html and content.html, you might have
<<script type="text/javascript">> <<!-- if (parent.location.href == self.location.href) window.location.href = 'frameset.html'; //-->> <</script>>
which would detect if the page was outside its frameset and rebuild it. Of course, this is a very simplistic example, but it gives the basic idea of frame building and the script can be expanded and a variety of tricks employed to preserve the state of the navigation and content pages.
All the efforts made in the last few sections reveal that frames really do have their downsides. While they may provide for stable user interfaces, they are not terribly bookmarking-friendly, they have more than occasional printing problems, and they are not well handled by search engines. As we demonstrated, you can certainly use JavaScript to solve the problems with frames, but it might be better just not to use them in many cases. Before concluding our discussion of frames, let’s take a final look at an interesting possibility using frames and JavaScript.
One aspect of frames that can be very useful is the ability to save variable state across multiple page views. As we previously saw with windows, it is possible to access the variable space of one window from another; the same holds for frames. Employing a special type of frameset that uses a small frame that is hard for a user to notice, we can create a space to hold variables across page loads. Consider, for example, the frameset in the file stateframes.html, shown here:
File: stateframes.html
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>State Preserve Frameset<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<frameset rows="99%,*" >> <<frame src="mainframe.html" name="frame1" id="frame1" frameborder="0" />> <<frame src="stateframe.html" name="stateframe" id="stateframe" frameborder="0" scrolling="no" noresize="noresize" />> <</frameset>> <</html>>
In this case, we have a very small frame called stateframe that will be used to save variables across page loads. The contents of mainframe.html, mainframe2.html, and stateframe.html are shown here. Notice how by referencing the parent frame we are able to access the hidden frame’s variable username on any page.
File: stateframe.html
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>Variables<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<body>> <<script type="text/javascript">> <<!-- var username = ""; //-->> <</script>> <</body>> <</html>>
File: mainframe.html
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>State Preserve 1<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<body onload="document.testform.username.value = parent.stateframe.username;">> <<h1 align="center">>JS State Preserve<</h1>> <<form name="testform" id="testform" action="#" method="get">> <<input type="text" name="username" id="username" value="" size="30" maxlength="60" />> <<input type="button" value="Save Value" onclick="parent.stateframe.username = document.testform.username.value;" />> <</form>> <<div align="center">> <<a href="mainframe2.html">>Next page<</a>> <</div>> <</body>> <</html>>
File: mainframe2.html
<<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">> <<html xmlns="http://www.w3.org/1999/xhtml">> <<head>> <<title>>State Preserve 2<</title>> <<meta http-equiv="content-type" content="text/html; charset=utf-8" />> <</head>> <<body>> <<script type="text/javascript">> <<!-- if (!(parent.stateframe.username) || (parent.stateframe.username == "")) document.write("<<h1 align='center'>>Sorry we haven't meet before<</h1>>"); else document.write("<<h1 align='center'>>Welcome to the page "+parent.stateframe.username+"!<</h1>>"); // -->> <</script>> <<div align="center">> <<a href="mainframe.html">>Back to previous page<</a>> <</div>> <</body>> <</html>>
While JavaScript can be used to preserve state and even create something as powerful as a shopping cart, it is not a good idea at all to use it in this fashion unless you are constantly making sure to address script being turned off mid-visit. Also, you may find the easy accessibility of script code a little too open for performing such an important task as preserving state information across pages. Until client-side scripting facilities become more robust, Web programmers probably should rely on traditional state management mechanisms such as cookies to maintain state between pages in a site.