7.0 Introduction

As with multiple windows, multiple frames are controversial among experienced web designers. Some love them, others refuse to use them. Dislike for framesets has a couple of origins. One dates back many years, when not all browsers supported them. Many veteran designers refused to accept framesets then and the prejudice continues. More recently, however, the pure and strict XHTML implementations omit frames from the document markup vocabulary. Forms and hyperlinks in validating documents cannot even include a target attribute that loads the result of a form submission or a linked document into another frame.

But the frames concept is not disappearing into oblivion. The XHTML specification includes a frame-specific version, and future work at the W3C will likely provide a fresh, XML-based frame markup vocabulary (currently called XFrames). At the same time, virtually every graphical user interface browser in use today supports HTML frames, and will do so for a long time to come. By setting the frameset element's border attribute to zero to create a seamless space, users may not even be aware of your frame structure.

Frames are especially useful in a few specific instances. The most common application is dividing a page into a large content frame and a smaller frame that acts as an index, table of contents, or site navigation menu. Such small frames might be along the left or right edge of the window, or sometimes as a horizontal slice at the top of the window. As the user scrolls and navigates content in the large frame, the smaller frame remains fixed and in position, ready for the next action. With the navigation frame remaining stable as the larger frame moves from page to page, the user does not have to wait for the navigation frame content to reload at every content page refresh. Another advantage to this relative stability is that you can use the framesetting document or other frame as a temporary repository of JavaScript data that persists while content pages change.

7.0.1 Frames as Window Objects

Since the days of the earliest scriptable browsers, the browser's object model exposed frames to scripts, but not in the supplemental way that the latest W3C DOM does. Each frame in the original system (still very valid in today's browsers) is treated as a window. After all, a frame contains a document just like a regular browser window. If you can gain scriptable access to the frame, most window object properties and methods apply directly to the frame as well.

The model for visualizing the relationships between frames in this manner uses a parent-child metaphor. The initial document that loads into the browserthe one containing the frameset element and all specifications for the frameset's makeupis the parent of the frames containing documents that the user sees. Parent and individual child frames are all treated as window objects.

A script running in the framesetting document can reference any of the child frames, and thus their documents, via the frames property of the parent window. The frames property contains a collection (array) of frame elements belonging to the parent:

window.frames[i]
window.frames["frameName"]
window.frameName

Two of the reference syntaxes rely on the name attribute of the frame elements being set. As for the numeric index, it is zero-based and follows source code order of the frame elements, even if the frames are nested deeply in framesets defined in the same top-level frameset document. In other words, regardless of the number of frameset elements defined in a framesetting document, there is only one parent, and as many child frames as there are frame elements defined in the document.

A more complex relationship exists, however, if one of the documents assigned to a frame's src attribute is, itself, another frameset. A script in the top-level frameset accesses this kind of nested frame through the following hierarchy:

window.frames["anotherFramesetName"].frames["nestedFrameName"]

Scripts operating inside a frame can reference both the parent frameset as well as sibling frames, but references must follow the hierarchy rather strictly. The parent keyword is the gateway to the parent framesetting document. For example, if the framesetting document contains a global variable named allLoaded, a script in one of the frames can read that value this way:

parent.allLoaded

For a script to access one of its siblings, the reference must include a parent frameset that both siblings have in common. For example, consider the following simple frameset:

<frameset cols="90, *">
    <frame name="navigation" src="navbar.html">
    <frame name="content" src="frameHome.html">
</frameset>

A script in the navigation frame can access the content frame with any of the following references:

parent.frames[1]
parent.frames["content"]
parent.content

Thus, a script in the navigation frame can instruct the content frame to scroll to the top as follows:

parent.frames["content"].scrollTo(0,0);

If the document loaded into the content frame was, itself, a framesetting document, the reference lengthens to include the pathway to one of its nested frames:

parent.frames["content"].frames["main"].scrollTo(0, 0);

To simplify references between frames within deeply nested framesets, you can always begin a reference from the topmost frameset, and then work your way down to the desired frame. That's where the top keyword is most useful:

top.frames["content"].frames["main"].scrollTo(0, 0);

A script in the deeply nested main frame can gain ready access to the highest navigation frame as follows:

top.frames["navigation"]

Perhaps more important than all of these referencing scenarios is the concept that referencing frames as windows gives you immediate access to the document of that window. For example, if a script in one frame wants to read the value of a text box in a sibling frame, the following backward-compatible syntax uses all original JavaScript and DOM Level 0 conventions:

var val = parent.frames["content"].document.entryForm.entryField.value;

Don't forget to include the document reference after the frame reference (a common mistake).

7.0.2 Framesets and Frames as Elements

In contrast to the frame-as-window scenario, the Internet Explorer and W3C object models allow scripts to reference the elements that create the framesets and frames. These objects have nothing to do with windows, per se, but everything to do with the ways the object models treat elements. Thus, these objects grant your scripts access to properties that mirror the tag attributes, such as a frameset's cols and rows properties and a frame's src and noResize properties. Access to these properties is handy when your scripts need to read or modify the attribute values. For example, Recipe 7.8 and Recipe 7.9 demonstrate how to adjust the dimensions of frames and even the column and row makeup of a frameset under script control.

The question that arises, however, is how a script that has a reference to a frame element can reach the document inside the frame. For this, you need to access a special property of the frame elementmore accurately, one of two possible properties, depending on the object model you are using. The IE model features a contentWindow property of a frame element, through which you can get to the document. For the W3C DOM, the contentDocument property references the document object inside the frame. Netscape 7 and later has implemented both, but if you need solid cross-browser support for IE 4 or later and Netscape 6 or later, use the following equalizer utility function:

function getFrameDoc(frameElem) {
    var doc = (frameElem.contentDocument) ? frameElem.contentDocument : 
        ((frameElem.contentWindow) ? frameElem.contentWindow.document : null);
    return doc;
}

7.0.3 Frames and Events

One common problem facing scripters who are new to frames is cross-frame scripts almost always rely on the other frame being loaded to operate correctly. If one frame loads quickly and references a form in a sibling frame but that form's document has not yet loaded, a script error greets the user. To compensate for this behavior, you must be mindful of the onload event handler characteristics for frames and framesets.

Just like a window, each frame element can have an onload event handler. The onload event for a frame fires when the complete contents of that frame's document have reached the browser. The frameset element, too, receives an onload event, but only after all of the nested frames' documents have loaded (and each of their onload events have fired). Therefore, the only sure way to trigger functions that operate across frames is to do so via the frameset's onload event handler (which you can bind as an attribute to the actual <frameset> tag).

Don't confuse this behavior with the kind of event bubbling that graces the IE and W3C DOM event models. The frameset element's onload event handler fires only the first time the frameset and its child frames load. If the user or a script changes the URL of one of the frames, that frame's onload event fires, but the frameset's onload event does not fire again. If you assign an onload event handler to the <frame> tag in the framesetting document, it executes each time the content of that frame changes (but only in IE 5.5 or later for Windows and Netscape 7 or later). For older browsers, the onload event handler must be defined in the body of the loaded document).

When documents in your framesets change a lot, and when they have substantial dependence on each other's scripting, you can also use another less elegant, but effective, technique to poll for the availability of another frame's document. Assuming that each document is served from the same domain and server (to satisfy the same-origin security policy), the onload event handler in each document sets a global Boolean variable, which acts as a flag for other frames' access. The variable is initialized at the top of the script as false, but the onload event handler sets it to true:

<script type="text/javascript">
var loaded = false;
...
</script>
...
<body onload="loaded = true">

Any script that needs to access content or scripts in this document can check first for the value of the loaded variable, and check it every second or so until it is true or the number of permitted attempts reaches your maximum tolerance level:

// count attempts to reach other frame
var tries = 0;
// the function that needs info from the other frame
function someFunc( ) {
    if (parent.otherFrameName.loaded) {
        // OK, other frame is ready; use it in this branch
        tries = 0;    // prepare for next access
        ...
    } else if (tries < 5) {
        tries++;
        // try again in 1 second
        setTimeout("someFunc( )", 1000);
    } else {
        tries = 0;
        alert("Sorry, we could not complete this task.");
    }
}

7.0.4 Hidden Frames

Occasionally, a site designer wants to use JavaScript to maintain an open connection with the server so that updated data from the server is reflected within a portion of the main page. While Internet Explorer's Data Binding technology is capable of doing this without any trickery, it is a Microsoft-only solution (with some additional restrictions for the Macintosh version of IE). For a cross-browser solution, other strategies are needed.

One solution that works in many mainstream browsers is to embed a faceless Java applet that keeps its own socket open with the server. Two-way communication between the applet and the JavaScript in the HTML page (dubbed "LiveConnect" by Netscape's first implementation of this feature) allows a free flow of data swapping between the two environments. Successful deployment of this solution, however, requires that the user choose to include the Java runtime environment with the browser.

An alternate solution is to use a hidden frame containing a document that periodically polls the server for updated data. Other than perhaps seeing some unexplained network activity, the user won't even know the unseen frame is refreshing itself every few seconds. The URL of the hidden frame could be to a CGI program running on the server. Such a CGI program then delivers a page with no renderable content, but with data embedded in the document in JavaScript or XML form (see Recipe 14.4 and Recipe 14.5 for further ideas about this data formatting). Additional scripts in the returned data, triggered by the onload event handler of the page, can copy data from the hidden frame to designated table cells or other HTML text nodes in the visible page.

7.0.5 Frame No-Nos

Just as windows and window objects could expose users to unscrupulous sites if security precautions were not in place, frames could offer a similar range of holes. But those, too, are blocked. Because frames are not as rich as windows with respect to their impact on the browser application, the list of things you can't do with frames is much shorter than for windows. Regardless of your good intentions, you cannot do the following things with frames:

Accessing document properties from other frames served by other domains

It could be potentially nasty business if a script in one frame could look into another frame and retrieve its URL or any of the HTML content. Browsers implement what is known as a same-origin security policy, which means that a script in one frame (or window) cannot access critical details (URL, DOM structure, form control settings, text content) from another frame (or window) unless both pages are delivered by the same domain and server, and arrived via the same protocol (such as HTTP or HTTPS, but not both).

Changing the content of the Address/Location text field

The URL shown in the field is of the framesetting document. You cannot place, say, the main content frame's URL in the field.

Setting a Favorites/Bookmarks entry to maintain the precise frameset composition

If a user navigates through your framed web site, the browser's own bookmarking facilities will preserve either the frameset or (through a contextual menu) a single frame's document. See Recipe 7.6 to assist in reconstructing a frameset from one frame's bookmark.