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 browser—the one containing
the frameset element and all specifications for
the frameset's makeup—is 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 element—more
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.
|