Using JavaScript within HTML frames, part one

Notes from May 23, 2005 class

HTML frames load multiple HTML pages into one window. Each page has its own set of window and document objects, as does the master page that holds them all. Frames can be a nightmare for a number of reasons, but they do have their useful sides as well. For example, using frames is one way of keeping your code in one location but being able to access it from several. This can also be a workaround for the fact that JavaScript can't ordinarily store information and pass it between separate html pages: since a frameset page holding multiple frame pages can remain open while the frame pages change, you can create code that appears to have more of a memory than we've been able to do thus far.

Comparing window object information within frames

Make a frameset with two rows:

<html>
<frameset rows="*,*" ID="MasterFrame">
<frame name="UpperWindow" src="upper.html">
<frame name="LowerWindow" src="lower.html">
</frameset>
</html>

Make two html pages:

upperframe.html lowerframe.html
<html>

<head><title>Upper frame</title>

<script language="JavaScript" type="text/javascript">

<!--

function window_onload()
{
alert("The name of the upper frame's window object is " + window.name);
alert("The location of UpperWindow's parent is " + window.parent.location.href);
}
//-->

</script>

</head>

<body onload="return window_onload()">

<h1>Frames and JavaScript</h1>
<p>Upper frame</p>
</body>

</html>

<html>

<head><title>Lower frame </title>

<script language="JavaScript" type="text/javascript">

<!--

function window_onload()
{
alert("The name of the lower frame's window object is " + window.name);
alert("The location of LowerWindow's parent is " + window.parent.location.href);
}
//-->

</script>

</head>

<body onload="return window_onload()">

<h1>Frames and JavaScript</h1>
<p>Lower frame</p>
</body>

</html>

What's going on?

<body onload="return window_onload()"> ensures that the function runs when the page loads.

The window_onload()function uses two properties of the window object. The first, window.name, takes the name we defined in the frameset HTML. The window.parent property gives you access to the parent window object's properties and methods. In this case, the parent window for either of these html pages is the html page that contains the frameset.

You might notice, if you load the working version of this script in both MS Internet Explorer and Mozilla/Firefox, that the order in which the browsers load the frames can vary. It's just another browser gotcha. Worse, each browser itself can be inconsistent in the order in which events fire within frames, so you can't even rely on MSIE or Mozilla always working the same. Always test your code carefully.

Coding between frames: using the frameset page as a module

If we put JavaScript into the master frameset page, it can be accessed by all pages within the frameset. This lets us be more modular with our code, writing code in one place that can be accessed from several places. It also lets JavaScript store information about events from multiple pages in just one place.

Make this frameset page:

<html>

<head><title>Frames and JavaScript</title>

<script language="JavaScript" type="text/javascript">

<!--

var pagesVisited = new Array(); //create a new array to hold this

function returnPagesVisited()
{

var returnValue ="So far you have visited the following pages\n";
var pageVisitedIndex; //create this variable
var numberOfPagesVisited = pagesVisited.length;
// sets this new variable to the number of items in the pagesVisited array

for (pageVisitedIndex = 0; pageVisitedIndex < numberOfPagesVisited; pageVisitedIndex++)
{

returnValue = returnValue + pagesVisited[pageVisitedIndex] + "\n";
/* this loops through the pagesVisited array, building up a string that contains a list of all the pages visited, which it will return as returnValue below */

}

return returnValue;
}

function addPage(fileName)
{

/* the fileName parameter that's passed in from the HTML frame page_a.html etc. will contain the full file name and path of the visited page. We need to strip out the path so we just have the file name. */

var fileNameStart = fileName.lastIndexOf("/") + 1;
/* find the position of the last "/" in the fileName string, and move one character to the right */

fileName = fileName.substr(fileNameStart);
/* the substr() method of the String object extracts everything from the character position fileNameStart to the end of the string. The substr() method normally takes two parameters, the starting character you want and the length of the string you want to extract. Leaving the second parameter out means all characters till the end of the string will be extracted, which is what we want here. */

pagesVisited[pagesVisited.length] = fileName;
/* add the file name into the array, using the .length property of the array to provide the next free index position. (That's because an array of, say, six items, will have an array length of six, but since the index numbers start at zero, the last current item in the array is arrayName[5], so arrayName[arrayName.length] will always be the next available position in the index. At last, that pesky index-starting-at-zero thing works in our favor! */

return true;

}

//-->

</script>

</head>

<frameset rows="*,*" ID="MasterFrame">

<frame name="FrameLeft" src="page_a.html">
<frame name="FrameRight" src="page_b.html">

</frameset>
</html>

Now make four versions of this page, page_a.html, page_b.html, page_c.html, and page_d.html:

<html>

<head><title>Page A</title>
<!-- remember to change the title to Page B for page_b.html, etc. -->

<script language="JavaScript" type="text/javascript">

<!--

function buttonShowVisited_onclick()
{
document.form1.txtaPagesVisited.value = window.parent.returnPagesVisited();
/* calls the parent window object's returnPagesVisited() function, and sets the value of the text area cited here to the output from that function. In other words, it goes to the parent window, runs the function, comes back to this page, finds the text area, and puts the text that function created into the textarea box. */
}

//-->

</script>

</head>

<body onload="window.parent.addPage(window.location.href);">
<!-- when this page loads, this onload function ensures its name gets put into the pagesVisited array in the frameset-->

<h1>This is Page A</h1>
<!-- remember to change the above to Page B for page_b.html, etc. -->

<p>
<a href="page_a.html">Page A</a> |
<a href="page_b.html">Page B</a> |
<a href="page_c.html">Page C</a> |
<a href="page_d.html">Page D</a>
</p>

<form name="form1">
<textarea rows="10" cols="35" name="txtaPagesVisited"></textarea>
<br />
<input type="button" value="List Pages Visited" name="buttonShowVisited" onclick="buttonShowVisited_onclick()">
</form>

</body>

</html>

Make four versions of this page, changing the code so that page_b.html says it's Page B, page_c.html says it's Page C, etc.

Here's a working example of these frames pages and their scripts.

Code access between frames

Suppose you had these nested framesets, creating the layout on the right:

Main frameset Nested frameset on the right, rightframe.html Page layout

<html>

<frameset>

<frame src="leftframe.html" name="left_frame">

<frame src="rightframe.html" name="right_frame">

</frameset>

</html>

<html>

<frameset>

<frame src="upperright.html" name="upper_right">

<frame src="lowerright.html" name="lower_right">

</frameset>

</html>

These nested frames will wind up with a hirearchy like this:

Sketching out this hierarchy makes it possible to figure out how to access parts of other pages in the hierarchy.

So, if I'm in bottom right, and I want to access the myURL() function in top right:

window.parent.topright.myURL();

If I'm in bottom right, and I want to access the myName() function in the left window:

window.parent.parent.leftwindow.myName();

You can also access it via its index in the frames[] array that the JavaScript Document Object Model creates whenever it enconters a frameset:

window.parent.parent.frames[0]

If the function nameThis() is in the master frame, and you're in the bottom right, this will work:

window.parent.parent.nameThis();

You don't need to identify the frame by name in this case, because window.parent.parent actually IS the frame that has the function.

Now, to access the value firstname in a form named form1 in the left frame, from the bottom right frame...

window.parent.parent.leftwindow.document.form1.firstname.value

Also, since window.parent.parent is the very top level of this frameset, we can just do this:

window.top.leftwindow.document.form1.firstname.value