Home Articles FAQs XREF Games Software Instant Books BBS About FOLDOC RFCs Feedback Sitemap
irt.Org

Related items

Re-directing access within frames - Revisited

Changing the location of another frame

Re-directing access within Frames

Internet Explorer Floating Frames

Re-directing access within Frames #2

You are here: irt.org | Articles | JavaScript | Frame | Re-directing access within Frames #2 [ previous next ]

Published on: Sunday 8th November 1998 By: Martin Webb and Jan Ehrhardt

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:

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:

contents
ergi87143548
title
main

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 Parametercontents Frametitle Framemain Frame
?contents.htmtitle.htmmain.htm
?page1.htmpage1.htmtitle.htmmain.htm
?page1.htm&page2.htmpage1.htmpage2.htmmain.htm
?page1.htm&page2.htm&page3.htmpage1.htmpage2.htmpage3.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 recognises 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/.

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.

Related items

Re-directing access within frames - Revisited

Changing the location of another frame

Re-directing access within Frames

Internet Explorer Floating Frames

©2018 Martin Webb