Articles | Redirecting Access
Within Frames #2 | back
Introduction
In the article Re-directing access within Frames we
explained how to ensure that your pages were correctly loaded within a frameset
even if a visitor was to request an individual frame of the frameset. Little did
we appreciate how popular the article would become and how how much feedback and
questions it would generate.
This follow-up article will, hopefully, address some of
these questions, and result in a more generic and robust solution. In particular
we hope the complete solution will address the following browser specific
problems:
- Not all browsers support the locations replace()
method, those that do also support the image[] object.
- The Opera browser only allows the replace method to
be used when an absolute URL is used.
- Opera 3.21 does not allow JavaScript variables from
other frames to be interrogated.
- Some versions of MSIE 3.x and MSIE 4.x under Windows
3.1, and MSIE 4.x under Windows 95 sometimes skip the contents of NOSCRIPT
tags even when Active Scripting is turned off.
- In some versions of NN and Opera when a script
interrupts the loading of a page to redirect the browser to a framed
version, the loading of the page does not always resume.
- Not all browsers support JavaScript, and in those
that do JavaScript maybe disabled - the same can be said for frames.
- In some versions of MSIE3 and MSIE4 using JavaScript
to output a frameset followed by a normal frameset in HTML causes the
browser to never finish loading the document.
- Opera 3.21 does not hide a frame in a frameset with
ROWS="100%,*".
- NN does not hide a frame in a frameset with
ROWS="100%,0".
- NN2 and beta versions of MSIE 3.01 under Windows 95
seem to retain the knowledge of previous frames and variable that are no
longer present in the parent frame, i.e. it cannot be relied upon when
testing if (parent.objectname).
- NN does not hide a frame in a frameset with
ROWS="*,100%"
- NN3.01 under Windows sometimes suffers from a timing
problem when detecting frames other than the first frame in the parent
frameset.
- NN 2.02, MSIE3 and Opera can fail to load a page
using top.location.href = relative_URL when the parent frame has a
different base HREF to the page performing the location change, this is a
feature and not a bug, the relative_URL is relative to the top.location and
not the current page.
- MSIE4.01 under Windows treats the name of a window
as tainted, i.e. it will not let a document from one server in one frame
access the name of a frame that holds a document from another.
Simplistic Frame Re-direction
The following example is a slightly reworked version of
the original code. It checks to see if the current document is loaded in a frame
(top == self) and whether the parents second frame is named myframeset.
If not then the location of the top frame is redirected to the frameset.htm
page with a search property equal to the location of the current page (location.href):
<html>
<head>
<script type="text/javascript" language="JavaScript"><!--
if (top == self || (parent.frames[1].name != myframeset))
top.location.href = 'frameset.htm?' + location.href;
//--></script>
</head>
<body>
...
|
And then in frameset.htm:
<html>
<script type="text/javascript" language="JavaScript"><!--
document.write('<frameset cols="50%,50%">');
document.write('<frame src="' + (location.search ? unescape
(location.search.substring(1)):'default.htm') + '">');
document.write('<frame src="rightframe.htm" NAME="myframeset">');
document.write('<\/frameset>');
//--></script>
</html>
|
Of course, due to the many reasons outlined in the
introduction, this will not work on all browsers.
Avoid being Fra-Framed-med
One problem with the above example is that, although
each frame will not be framed by the wrong frameset, the same cannot be said of
the correct frameset.htm frameset. This can be easily remedied with a
subtle change to frameset.htm:
<HTML>
<script type="text/javascript" language="JAVASCRIPT"><!--
if (top != self)
top.location.href = location.href;
else {
document.write('<frameset cols="50%,50%">');
document.write('<frame src="' + (location.search ? unescape
(location.search.substring(1)):'home.htm') + '">');
document.write('<frame src="leftframe.htm" name="myframeset">');
document.write('<\/frameset>');
}
//--></script>
</html>
|
Not only can you ensure that the correct frameset is
loaded, but also you can avoid your frameset being framed by someone else.
Multiple Frame Version
In this more complicated example we are going to be
using a frameset definition which contains more than two frames, based on the
following HTML in the file frameset.htm:
<frameset cols="50%,50%">
<frameset rows="100%,*">
<frame name="contents" src="contents.htm">
<frame name="ergi87143548" src="blank.htm">
</frameset>
<frameset rows="50%,50%">
<frame name="title" src="title.htm">
<frame name="main" src="main.htm">
</frameset>
</frameset>
|
Which should produce a frameset similar to:
Although the ergi87143548 frame will be
effectively invisible.
The contents of contents.htm, title.htm
and main.htm should all begin with the following:
<html>
<head>
<script type="text/javascript" language="JavaScript"><!--
if ((top == self) || (parent.frames[1].name != 'ergi87143548'))
top.location.href = 'frameset.htm?contents.htm&title.htm&main.htm';
//--></script>
</head>
<body>
...
|
If the page is not loaded within a frameset or the
parents second frame (remember the frames index starts at zero) is not named ergi87143548
then the browser is redirected to the frameset.htm file with a list of
all the pages to load. The frame ergi87143548 has been named this way
to avoid the possibility of any one else's page framing our frame with another
frame called ergi87143548. As more and more people make use of the
original frameset redirection code in the previous article Re-directing access
within Frames then the more likely this is. To further avoid the likelihood of
this occuring you should rename this frame and alter any reference to this frame
to some randomly chosen frame name.
If the names of the pages being passed across to
frameset.htm include non alphanumeric characters then you may need to first
escape them using the escape() method:
top.location.href = 'frameset.htm?' + escape('contents.htm') +
'&' + escape('title.htm') +
'&' + escape('main.htm');
|
To actually receive the page values in the frameset.htm
page we need to add some JavaScript code:
<html>
<script type="text/javascript" language="JavaScript"><!--
var passed = location.search ? unescape
(location.search.substring(1)) + '&' : '';
var myContents = passed ? passed.substring
(0,passed.indexOf('&')) : 'contents.htm';
passed = passed.substring(passed.indexOf('&')+1);
var myTitle = passed ? passed.substring(0,passed.indexOf
('&')) : 'title.htm';
passed = passed.substring(passed.indexOf('&')+1);
var myMain = passed ? passed.substring(0,passed.indexOf
('&')) : 'main.htm';
if (top != self)
top.location.href = location.href;
else {
document.write('<frameset cols="50%,50%">');
document.write(' <frameset rows="100%,*">');
document.write(' <frame name="contents" src=
"' + myContents + '">');
document.write(' <frame name="ergi87143548" src=
"blank.htm">');
document.write(' <\/frameset>');
document.write(' <frameset rows="50%,50%">');
document.write(' <frame name="title" src=
"' + myTitle + '">');
document.write(' <frame name="main" src=
"'+ myMain + '">');
document.write(' <\/frameset>');
document.write('<\/frameset>');
}
//--></script>
</html>
|
If a search string is passed to the above frameset.htm
document, then it manipulates the search parameter to retrieve upto three
values. If any of the values are missing them it defaults to the three files: contents.htm,
title.htm and main.htm. If any one or two of the values are
missing then depending on how they are passed to frameset.htm will
dictate how and where they are loaded.
For example:
| Search Parameter |
contents Frame |
title Frame |
main Frame |
| ? |
contents.htm |
title.htm |
main.htm |
| ?page1.htm |
page1.htm |
title.htm |
main.htm |
| ?page1.htm&page2.htm |
page1.htm |
page2.htm |
main.htm |
| ?page1.htm&page2.htm&page3.htm |
page1.htm |
page2.htm |
page3.htm |
So as you can see, you have almost complete control of
which frame contains what page.
Rewriting History
Each time you change the browsers location using location.href
= 'page.htm'; then you add an entry to the browsers history object - when
the user presses the back button, this JavaScript code is extremely likely to
change the location again - resulting in the user becoming trapped in your
frameset.
It is fairly easy to alter your code to allow the use
of the location objects replace() method, which replaces the current
location in the browsers history object with another page - thus resulting in
the user being able to back out of your frameset. To check whether the browser
supports the replace() method check for the existence of the documents image
object:
if (document.images)
location.replace('nextpage.htm');
else
location.href = 'nextpage.htm');
|
The Opera browser supports the replace()
method, but only if the URL being used is an absolute URL and not a relative URL
as used above. So change it to. To avoid hardcoding the absolute url into the
script you can make a generic script that calulates the absolute path using:
newURL = location.protocol + '//' + location.host +
location.pathname.substring
(0,location.pathname.lastIndexOf('/')) + '/newpage.htm'
|
We can now adapt our existing redirect scripts to take
this into account:
<html>
<head>
<script type="text/javascript" language="JavaScript"><!--
if ((top == self) || (parent.frames[1].name != 'ergi87143548')) {
var newURL = location.protocol + '//' + location.host +
location.pathname.substring(0,location.pathname.lastIndexOf('/'))
+ '/frameset.htm?contents.htm&title.htm&main.htm'
if (document.images)
top.location.replace(newURL);
else
top.location.href = newURL;
}
//--></script>
</head>
<body>
...
|
And in frameset.htm:
<HTML>
<script type="text/javascript" language="JavaScript"><!--
var passed = location.search ? unescape
(location.search.substring(1)) + '&' : '';
var myContents = passed ? passed.substring(0,passed.indexOf
('&')) : 'contents.htm';
passed = passed.substring(passed.indexOf('&')+1);
var myTitle = passed ? passed.substring(0,passed.indexOf
('&')) : 'title.htm';
passed = passed.substring(passed.indexOf('&')+1);
var myMain = passed ? passed.substring(0,passed.indexOf
('&')) : 'main.htm';
if (top != self) {
if (document.images)
top.location.replace(location.href);
else
top.location.href = location.href;
}
else {
document.write('<frameset cols="50%,*">');
document.write(' <frameset rows="100%,*">');
document.write(' <frame name="contents" src=
"' + myContents + '">');
document.write(' <frame name="ergi87143548" src=
"blank.htm">');
document.write(' <\/frameset>');
document.write(' <frameset rows="50%,50%">');
document.write(' <frame name="title" src=
"' + myTitle + '">');
document.write(' <frame name="main" src=
"'+ myMain + '">');
document.write(' <\/frameset>');
document.write('<\/frameset>');
}
//--></script>
|
No JavaScript? No Frames?
Some browsers do not support JavaScript. In all
browsers that support JavaScript it is possible to disable it. There are still a
few browsers around in use today that do not support frames. The Opera browser
even allows frames to be disabled. Therefore, you should always provide some
support in your documents for these instances. The frameset.htm
document can be extended to include the following:
<noscript>
<frameset framespacing="0" border="false" frameborder=
"0" cols="50%,*">
<frame name="contents" scrolling="no" marginwidth=
"0" marginheight="0" src=
"contents.htm">
<frameset rows="50%,*">
<frame name="title" scrolling="no" marginwidth=
"0" marginheight="0"
src="title.htm">
<frame name="main" scrolling="no" marginwidth=
"0" marginheight="0"
src="main.htm">
</frameset>
</frameset>
</noscript>
<body>
<p>
This page uses frames, but your browser does not support them
- or in the case of Opera - frames have been disabled
</p>
</body>
</html>
|
Notice the inclusion of a duplicate HTML frameset. This
ensures that for browsers that don't support JavaScript, or where JavaScript has
been disabled, then at least the default frameset is loaded. Therefore, in this
example, where the frameset has been fully defined and displayed, it would be
most unusual for the browser to display a duplicate frameset. If you don't
believe it, then try out the working eaxmple at the end of this article - load
the frameset.htm link into NN2 with JavaScript enabled and then disabled and see
the frameset rendered in both. Finally for those browsers that don't support
frames some simple HTML to inform the visitor that they are missing something.
The NOFRAMES tags were introduced in NN3 and provides a
way of catering for browsers that don't support frames, or in the case of the
Opera browser, where frames have been disabled. Those browsers that support
frames, and where frames are enabled, will ignore the contents of the NOFRAMES
tags.
The NOSCRIPT tags were introduced in NN3 and provides a
way of rendering HTML on the page when the browser has JavaScript disabled.
Those browsers that don't know anything about JavaScript at all will simply
render the contents within the NOSCRIPT tags. NN2 which does support JavaScript,
does not know anything about the NOSCRIPT tags, so it will usually render
whatever is within the tags.
However, in some versions of MSIE3 and MSIE4 this is
not always the case. If we include the HTML frameset within NOSCRIPT tags then
the browser totally ignores the content in the NOSCRIPT tags even it active
scripting is disabled. There are some versions of MSIE3 and MSIE4, where using
JavaScript to output a frameset followed by a normal frameset in HTML without
the NOSCRIPT tags causes the browser to never finish loading the document.
Using NOSCRIPT and NOFRAMES would cause MSIE3
and MSIE4 to display nothing at all. We had to choose: omitting the NOSCRIPT
(result: endless loading in some versions of MSIE) or omitting the NOFRAMES
(result: BODY-part displayed in some versions of MSIE). Skipping the NOSCRIPT
content seems to be a bug in these versions of MSIE, the better choice is to
omit the NOFRAMES tags. You can achieve the same effect without using NOFRAMES
tags, by just placing the code inbetween BODY tags, which is then effectively
ignored by browsers that render the frames.
Printing frames
We sometimes receive the following complaint about the
use of JavaScript to redirect frames:
If you use Netscape 4+ then you can not print out those
pages that have been redirected. You get a printing error that states the
documents have no data.
The Netscape Navigator 4 print engine recognizes
JavaScript and attempts to interpret it. When the user requests a print of a
frame, then the browser effectively loads that frame into a hidden window, with
the result that the simple code:
if (top == self) {
// load page
}
|
causes the document to be reloaded (or attempt to be
reload), which then attempts to replace the document in this hidden window with
nothing. You can check this yourself. If you load a simple document into the
browser:
<body onLoad="alert('Hello World!')">
|
and then when you request a print preview, the alert
message is displayed. This is maybe as a result of Netscape's attempt to
re-address the known problem on earlier browsers whereby any HTML generated
using JavaScript wouldn't appear when printed.
We originally couldn't work out how to work around this
problem - until we were pointed to the Joust Outliner at http://www.alchemy-computing.co.uk/joust/.
NOTE: The above link will open in a new window.
Ths Joust Outliner uses similar techniques described in
this article to ensure that individual pages are enclosed within the appropriate
frameset.
Ivan Peters has kindly agreed to allow us to include
his Netscape Print detection technique within this article.
Basically iy makes use of the two window object
propeties: innerHeight and innerWidth - which are according to
the Netscape Client Side JavaScript Reference:
innerHeight:
Specifies the vertical dimension, in pixels, of the window's content area.
innerWidth:
Specifies the horizontal dimension, in pixels, of the window's content area.
The assumptiom is, that when a document is being
prepared for printing, that the document is either not loaded in a window, or
the two properties have values equal to zero.
The following technique makes use of this assumption:
if (document.layers && (self.innerHeight == 0 && self.innerWidth == 0))
// printing
|
We can now adapt our redirect scripts to include this
check. If the document is being printed, then we simply do not redirect the
document.
Bug Busting
Some versions of MSIE treat the name property of a
window, and hence a frame as tainted, i.e. it cannot be accessed by a script
running in a document from another server. It is possible to detect the
existance of a named frame using if (parent.framename), however this
isn't a reliable technique under NN2 and IE3.0 Beta. With the various bugs
described in the introduction it is possible to provide a solution that works
around all the problems, by providing an image check named ergi87143548
for those browsers that support images, and a frame named ergi87143548
for those that don't:
<html>
<script type="text/javascript" language="JavaScript"><!--
var passed = location.search ? unescape
(location.search.substring(1)) + '&' : '';
var myContents = passed ? passed.substring(0,passed.indexOf
('&')) : 'contents.htm';
passed = passed.substring(passed.indexOf('&')+1);
var myTitle = passed ? passed.substring(0,passed.indexOf
('&')) : 'title.htm';
passed = passed.substring(passed.indexOf('&')+1);
var myMain = passed ? passed.substring(0,passed.indexOf
('&')) : 'main.htm';
if (top != self) {
if (document.images)
top.location.replace(self.location.href);
else
top.location.href = self.location.href;
}
else {
if (document.images) {
ergi87143548 = new Image();
document.write('<frameset framespacing="0" border=
"false" frameborder="0"
cols="50%,50%">');
document.write(' <frame name="contents" scrolling=
"no" marginwidth="0"
marginheight="0" src="' + myContents + '">');
document.write(' <frameset rows="50%,50%">');
document.write(' <frame name="title" scrolling=
"no" marginwidth="0"
marginheight="0" src="' + myTitle + '">');
document.write(' <frame name="main" scrolling=
"no" marginwidth="0"
marginheight="0" src="'+ myMain + '">');
document.write(' <\/frameset>');
document.write('<\/frameset>');
}
else {
document.write('<frameset framespacing="0" border=
"false" frameborder="0"
cols="50%,50%">');
document.write(' <frameset rows="100%,*">');
document.write(' <frame name="contents" scrolling=
"no" marginwidth=
"0" marginheight="0" src="' + myContents + '">');
document.write(' <frame name="ergi87143548" scrolling=
"no" marginwidth=
"0" marginheight="0" src="blank.htm">');
document.write(' <\/frameset>');
document.write(' <frameset rows="50%,50%">');
document.write(' <frame name="title" scrolling=
"no" marginwidth=
"0" marginheight="0" src="' + myTitle + '">');
document.write(' <frame name="main" scrolling=
"no" marginwidth=
"0" marginheight="0" src="'+ myMain + '">');
document.write(' <\/frameset>');
document.write('<\/frameset>');
}
}
//--></script>
<noscript>
<frameset framespacing="0" border="false" frameborder=
"0" cols="50%,50%">
<frame name="contents" scrolling="no" marginwidth=
"0" marginheight="0" src=
"contents.htm">
<frameset rows="50%,50%">
<frame name="title" scrolling="no" marginwidth=
"0" marginheight="0" src=
"title.htm">
<frame name="main" scrolling="no" marginwidth=
"0" marginheight="0" src=
"main.htm">
</frameset>
</frameset>
</noscript>
<body>
<p>
This page uses frames, but your browser does not support them -
or in the case of Opera - frames have been disabled
</p>
</body>
</html>
|
One side effect of this, is that as all versions of
Opera support images, then the image version of the frameset is used, i.e. the
one without a hidden frame, which therefore prevents the complication with
"visible" hidden frames. We can simply use the well established
ROWS="100%,*" technique for browsers that don't support images (NN2
and MSIE3) in the hidden frame version of the frameset. And another side effect
is that we avoid the timing problems in NN3.01 because the ergi87143548
image is always declared before the frameset.
It is now possible to check the parent frame for either
the image named ergi87143548 or the frame named ergi87143548.
However, before we do, there is one further problem. The frame detection code
causes the current downloading page to be interrupted and reloaded within a
frameset. This has been reported to cause problems where the loading of a large
page when reloaded within the frameset fails to download completely. What we
need to do is invoke the frame detection code after the page has
completed loading, either through the use of the onLoad event handler, or by
placing the JavaScript code at the end of the document.
<html>
<head>
<script type="text/javascript" language="JavaScript"><!--
function checkforframe() {
if (document.layers && (self.innerHeight ==
0 && self.innerWidth == 0)) return;
if ((top == self) || ((document.images) ?
(parent.ergi87143548 ? false : true)
: (parent.frames[1].name != 'ergi87143548'))) {
var newURL = self.location.protocol + '//
' + self.location.host + self.location.pathname.substring
(0,self.location.pathname.lastIndexOf('/')) +
'/frameset.htm?contents.htm&title.htm&main.htm';
if (document.images) top.location.replace(newURL);
else top.location.href = newURL;
}
}
//--></script>
</head>
<body onLoad="checkforframe()">
...
|
Working Example
You can try out the working example for yourself:
Load the complete frameset.
Load the individuals documents on their own: contents,
title or main.
Load the two of the documents into an alien
frameset.
Load three of the documents into another servers
frameset.
The Small Print
Because of the way MSIE and Opera work offline, the
above frame re-direction techniques will not work offline.
As you can see, this was a difficult article to write.
If you find any further problems with the techniques used then please use the
feedback link at the bottom of this page.
Articles | Redirecting Access
Within Frames #2 |
back
|