Previous page
BACK

Part 5 - Using Frames
(And playing with balls!)

 
Contents
  Making frames
 How?
 Using them!
 Details
 Summary

Making Frames

Frames can be cute. Often they are abused by webpage designers, in fact, usually they are abused. The big problem with frames is that they can be clumsy, cramping already busy pages into smaller spaces. In addition, having multiple frames, each with its own tiny scroll bar, is a recipe for confusion. There are other disadvantages. Having said this, you can see that a page based on our previous demonstration would be ideal for say a "shopping cart" .. all it uses is a little JavaScript, without even the 'inconvenience' of things like cookies or CGI scripts. You could simply click on items in a "shopfront" in the right frame, and the actual pictures (with information about the item selected) could be placed in the "shopping basket" on the left. (Later on, at "checkout" a CGI script would be a good idea). Let's find out about frames.

How do we make frames?

The fundamental tag we use to make a frame is (surprisingly enough) the <frame> tag, but this is not our most important component. What is? The <FRAMESET> tag. (It's so important, we've put it in capitals)! Here's an example:

Creating a set of frames
<FRAMESET cols="60,*" name="myFrameset">
  <frame src="left5.htm" name="left5" scrolling="NO" >
  <frame src="right5.htm" name="right5" scrolling="AUTO">
</FRAMESET>

This is the frameset that was used to create our current frame. See how we make two frames side-by-side, the left frame 60 pixels in width, the right taking up the rest - we just say cols="60,*". Had we wanted three vertical frames, say twenty percent of the width, thirty percent and fifty percent, we would have said:

cols="20%,30%,50%"

Notice how we can give an explicit size (either in pixels or as a percentage) or we can simply use * to indicate "all of the rest". Can you guess how we set up frames stacked one on top of the other, as opposed to side-by-side? Yes, instead of saying cols we say rows. The syntax is otherwise the same.

What if we wanted both rows and columns, something laid out like this:

a horizontal frame
a second horizontal frame below the first
three frames...below this...next to one another

There are several ways of doing this. Perhaps the simplest is:

A nested set of frames
<FRAMESET rows="33%,33%,*" name="FRAMEPILE">
  <frame src="top.htm" name="topFrame" >
  <frame src="middle.htm" name="midFrame">
  <frameset cols="33%,33%,*" name="bottomSideBySideFrames">
       < frame src="left.htm" >
       < frame src="piggie.htm" >
       < frame src="right.htm" >
  </frameset>
</FRAMESET>

You get the idea - we have three frames stacked one above the other, only the bottom 'frame' is actually a frameset which itself contains three frames, side by side.

You can get even more tricky if you want, by having the bottom component as a frame rather than a frameset (loading a document called, say, "bottom.htm" into the bottom frame), and then having this bottom document made up of another set of three frames side by side. You can even have multiple levels of frames - just beware that you don't get lost in a maze of your own making. Generally you'll find that simple frames are best.

Properties of frames

When we described top-level objects (the window and navigator objects) in part three of our tutorial, we rather glossed over frames. Let's remedy the omission. frames is part of the window object, and is an array of all the frames in that window. A most fundamental concept is:

In JavaScript, a frame has all the properties of a window!

We therefore know that each and every frame, even though it is simply an item in the frames array, has properties that we expect a window to have. From our discussion in part three, we know, for example, that a window has a .location, a .document and a .name. So does every frame! Each and every frame has a parent, a history, an opener, and so on.

When we define a frame in HTML, then it has several other properties. . These include:

  • src="whatever.htm" which (as we know from above) gives us the HTML file that is loaded into the frame;
  • name="fred" which is just a convenient way of referring to the frame (more of this later);
  • noresize (This prevents the user from resizing the frame)
  • scrolling="" (Options are YES, NO or AUTO)
  • marginheight="" (The vertical offset in pixels of the top and bottom margins)
  • marginwidth="" (Similar to marginheight, but gives the width of the left and right margins)

There seems to be no way of altering the noresize, scrolling and margin properties of a frame once it's loaded (If you know how, tell me)! However, just as with a window, we can load another document into a frame called, say, "fred" just by saying

fred.location = "newdoc.htm"

Where "newdoc.htm" is the name of the new document we wish to load! Treat a frame just like a window, and you'll rarely be disappointed.

Similarly, there is NO way (that we know of) of changing the width of a frame within a frameset, once the frameset is loaded. You have to reload a whole new document with a different frameset!

Targets

Targets are powerful ways of moving information around. You can include the TARGET= instruction in the following:
  • an anchor tag: <a href="somewhere" TARGET="targetname" >
  • a FORM: <FORM TARGET="targetname">
  • something called the <BASE TARGET="targetname" > tag.

The "targetname" in the above examples is simply the name of a frame. But we know that frames and windows are treated the same way, so you could just as easily specify the name of a window! Any TARGET that is specified becomes the destination for data from a form, or the results of clicking on an anchor!

What does the BASE tag do? This is a powerful tag used to specify the default target in an HTML document. Once this has been specified, all targets of anchors go to that target (but you can override it).

There are certain special targets. Here they are:

Special target names
Special Target Meaning
_blank Force creation of a new window
_self Load to the same window
_parent Load to immediate parent (in the frameset)
_top Load to full body of window, regardless of frame nesting

Note that you don't have to have JavaScript enabled to use targets. A lot of the more fiddly things that you can do with frames are however made considerably easier (or indeed, possible) with JavaScript.

How we did it!

We've chosen only one of many possible ways to get two frames to talk to one another. Think about it - provided you know the name of a frame there must be dozens of ways you can write information from one frame into another (This is a consequence of the inexpert way that frames and indeed JavaScript have been designed - data hiding and encapsulation are not conspicuous properties). Each of these ways is far from ideal - ideally one should just be able to open up a pipe between one frame and another, and pour information into one end, processing it as it comes out the other end of the pipe! (That we cannot do this is likewise a symptom of clumsy design). Well, here's how we did it:

Plan

  1. Make a set of frames (left and right);
  2. Create a document in the right frame with five pictures of snooker balls inside it. Each picture should have an anchor associated with it. When you click on the anchor, a JavaScript function is invoked. The function passes information to the LEFT frame. The information is a list of balls to draw.
  3. The document in the LEFT frame accepts the information provided, and draws the stated balls.
  4. When five balls have been drawn, the LEFT frame document forces loading of a new document into the right frame.

And that's basically that. Of course there are frills - for example, once a ball has been transferred out, we don't want it to still appear in the right frame. But let's examine the basics:

1. Making a frameset

We've already done this. Here it is:

Creating a set of frames
<FRAMESET cols="60,*" name="myFrameset">
  <frame src="left5.htm" name="left5" scrolling="NO" >
  <frame src="right5.htm" name="right5" scrolling="AUTO">
</FRAMESET>

Note that we've not only specified the HTML documents, but also named the individual frames - they're called "left5" and "right5".

2. A document on the right

This is easy. Here is the body of a document that contains just one ball. You can easily generalise it to five:

The body of the right document

<body>
<a href="null.htm" onClick="Xfer('0,red');return false">
  <img name="redball" src="images/redball.gif"
       width="100" height="98" alt="Red snooker ball"
       border="0"></a>
<p>
<div align="center">
 <font size="5">Click on each of the above balls
 </font><br> (in any order you want)!
</div>
</body>

See how we "hijack" the <a> anchor tag and transfer control to the function Xfer, which we'll define in a moment. The datum that we transfer using this function is the string '0,red'. We'll use the zero to indicate which image we're dealing with, and "red" to draw a new image. (There's a bit of redundancy here, but in the end it makes things shorter). Let's see how we do this:

The transfer function, Xfer
function Xfer (itm)     //TRANSFER DATA (balls) TO LEFT PANEL.
 {                      // itm : new data to append.
 var info;              //information to send to left panel
 var idx;               //index of current image
 var comma;             //position of comma within *itm*

 comma = itm.indexOf(",");              //find first comma in itm
 idx   = itm.substring(0,comma);        // pull out index
 idx   = parseInt(idx);                 //convert to integer
 itm   = itm.substring(1+comma);        //clip integer off itm

 info = parent.frames[0].location.hash; //pull out current data string
 if (info.length > 0)
   { info=info.substring(1);            //clip off leading "#", if present.
   };
   // THIS CODE DOESN'T WORK - WE HAVE TO ADD SOMETHING *HERE* !
 itm = itm + "_";                       //add trailing underscore!
 parent.frames[0].location = "left5.htm#" + itm + info; //"SEND INFORMATION"
 }

How does this work? We know the format of the string we've submitted in itm, for example "0,red". We split this into two, a number called idx, and the colour of the ball, in this case "red". The following statement needs some explanation:

info = parent.frames[0].location.hash;

We know that (just like a window) one of the properties of a frame is its parent. We also know that we are currently in a frame (the right frame), and that the right and LEFT frames both lurk inside the same frameset. It's a reasonable guess therefore that the right frame can get information about the LEFT frame by asking the parent (the frameset?) for that information. Here we say:

"go to the parent and get the array of frames. The first frame we referred to when we made the frameset was the LEFT frame, so this is the one we want - frames[0]. Give me that frame, and then take the hash part of its location - that's really what I want.

So what we are doing is really just what we did on the very first page of our tutorial, where we used a hash to move information between web-pages! At least we're consistent!

The rest of the code then becomes pretty transparent - all we need to do is append the new information, and force our browser to reload the left window by saying:

parent.frames[0].location = "left5.htm#" + itm + info;

where the hash after the web-page name contains all the information. We use underscores ( "_" ) to separate data items. There's only one problem with the above code, and that is IT DOESN'T WORK (as we indicated in the code itself)!

It turns out that, both in Netscape and IE, if a document is already squatting in its frame like a toad, simply changing the hash is insufficient to force a reload of the document! No problem - we simply write some other arbitrary stuff into the document, forcing a reload when we then do our location = thing. Here is what we insert:

What we insert
 parent.frames[0].document.open();     //open document
 parent.frames[0].document.write(" "); //write blank to document
 parent.frames[0].document.close();    //close it!

The above code forces writing of a new document. Our code will then succeed.

3. Receiving information on the LEFT

The code for this "reception" is fairly straightforward:

What we insert
    var info;           //the information string from the right 
    var datum;          //an individual datum
    var cutat;          //where we will cut the information string
 info = window.location.hash.substring(1);      //pluck out data
 cutat = info.indexOf("_");
 while (cutat > -1)                             //for each data item
   { datum = info.substring(0,cutat);           //pluck out ONE datum
     info = info.substring(1+cutat);            //trim it off front
     document.write ("<img src='images/"
                     + datum
                     + "ball.gif' width='50' height='49' alt='"
                     + datum
                     + " ball' >");
     cutat = info.indexOf("_");                 //next item
   };

Okay, not very elegant code. It has the merit of actually working. How? Well, we pull out the hash (as we did on page two of our tutorial), separate the individual data items, which are just colours like "red", "green" and so on, and then turn this bare colours into images. The single line:

   document.write ("<img src='images/"
                    + datum
                    + "ball.gif' width='50' height='49' alt='"
                    + datum
                    + " ball' >");

probably deserves some analysis. We are dynamically writing an image tag, so that the string "red" is turned into: <img src='images/redball.gif' >

with appropriate width, height and alt components. (We halve the ball size compared to the original frame). Not that difficult, is it? Note that we could have passed the whole file name, with or without the path, but in this example we decided not to, mainly for simplicity's sake.

4. Loading a new right document

This is easy. All we need to do is count the number of balls coming into the left document, and then when this reaches five, say:

parent.frames[1].location = "5.htm" // load new RIGHT frame at end

5. Frills

We still haven't fixed up a few problems. The first one is that with the above code, the images will be drawn on the LEFT but still remain on the right. This is easily remedied, and also explains why we passed an index to Xfer. Here is the code we'll use, inside the Xfer function:

document.images[idx].src="images/noball.gif"; //kill image in right frame

The idea is that we replace the image source with a blank image called "noball.gif", and then our browser conveniently overwrites the original image with .. nothing! (We found out about this convenient property of images at the end of part 4 of our tutorial). You'll notice that on most browsers the focus will still be at the vanished image, which is not that pretty - you can fix this if you want.

Another problem is that, even when the image has vanished (or been replaced by a blank image) if you click on that area, then another "ball" will be transferred. This too can be fixed. We simply modify the last part of Xfer to:

 if (info.indexOf(itm) < 0)             //if item not already in info..
  { parent.frames[0].document.write(" "); //open and write blank document
    parent.frames[0].document.close(); //close it!
    itm = itm + "_";                       //add trailing underscore!
    parent.frames[0].location = "left5.htm#" + itm + info; //"SEND INFORMATION"
  };

In other words, we look for the "new" item in the old information string, and if it's present, do NOT update the string!

Another 'upgrade' is possible involving the following code:

<a href="null.htm" onClick="Xfer('0,red');return false">

where we "hijacked" the anchor for the picture of the red snooker ball. We all know that our code is perfect (hmm) but were the Xfer function to crash for some reason, then our ignorant browser will probably still bash on headlong and try and load the file "null.htm". We could replace this with a more intelligent name like "err.htm" and have a cute little error message ("Hmm. We seem to have screwed up somewhat?"), if we so wished!

The 'final' functions

Here they are:

Send Information
function Xfer (itm)     //TRANSFER DATA (balls) TO LEFT PANEL.
 {                      // itm : new data to append.
 var info;              //information to send to left panel
 var idx;               //index of current image
 var comma;             //position of comma within *itm*

 comma = itm.indexOf(",");              //find first comma in itm
 idx   = itm.substring(0,comma);        // pull out index
 idx   = parseInt(idx);                 //convert to integer
 itm   = itm.substring(1+comma);        //clip this off itm
 document.images[idx].src="images/noball.gif"; //kill image in right frame

 info = parent.frames[0].location.hash; //pull out current data string
 if (info.length > 0)
   { info=info.substring(1);     //clip off leading hash, if present.
   };

 if (info.indexOf(itm) < 0)             //if item not already in info..
  { parent.frames[0].document.write(" "); //open and write blank document
    parent.frames[0].document.close(); //close it!
    itm = itm + "_";                       //add trailing underscore!
    parent.frames[0].location = "left5.htm#" + itm + info; //"SEND INFORMATION"
  };
 }

and..

Receive
    var info;
    var datum;
    var cutat;
    DATACOUNT=0;                                   //number of items
 info = window.location.hash.substring(1);      //pluck out data
 cutat = info.indexOf("_");
 while (cutat > -1)                             //for each data item
   { datum = info.substring(0,cutat);           //pluck out ONE datum
     info = info.substring(1+cutat);            //trim it off front
     document.write ("<img src='images/" + datum
       + "ball.gif' width='50' height='49' alt='" + datum + " ball' >");
     cutat = info.indexOf("_");                 //next item
     DATACOUNT ++;                              //bump count
   };
if ( DATACOUNT == 5 )
  { parent.frames[1].location = "5.htm" // load new RIGHT frame at end
  };

Okay, okay. We cheated (again)! The functions we used are slightly more complex, so that (1) the pretty balls in the text you're reading are in the same order as the ones you chose, and (2) you can click on the balls in the left frame to go to areas in the text! Try it. Then view the source to see how little modification was needed to achieve this!

Using frames

We've only shown you one way you might use frames. Although our code is far from optimal, it does give you a powerful basis for communicating between frames without resorting to CGI scripts or cookies. There are many other ways - you may wish to explore these, for example trying to use the target property of a page's location to transfer information (good luck!), or simply writing from the input of a form in one frame to a textarea in another frame.

Why not use CGI? The most compelling reason not to use CGI is speed. Especially if the server on which the CGI script resides is overburdened, our approach is likely to be far faster. (You also have to learn how to write CGI scripts if you choose this option).

A few other catches:

  • In some versions of Netscape (but not IE) if you resize the left frame (that you are now looking at), then the images of the balls for some reason become corrupt. {We can't explain this}. The solution seems to be not to use resizeable frames.

  • Contrariwise, MSIE seems to mess up the background colour of the left frame, after the initial load. This is because MSIE ignores the <body> tag if writing to the frame. A solution is to write this dynamically.
    (There's even a fix for naïve browsers that allows them but not JavaScript enabled ones to see " </head><body> " - see how we did this in the source of "part2j.htm" !)

  • If you go back up to the top of this page, and click on the "BACK" image, you'll see that the frames magically disappear! How did we do this? The trick is as follows:

    <A HREF = "javascript:parent.location.href = 'part4.htm'">

    What we do is to force a JavaScript statement with the leading javascript: command. (You can do this!) What does the javascript following this do? It forces the part4.htm file into the whole parent frame, replacing the current frameset completely! Sneaky.

  • You might have noticed that when you clicked on your first snooker ball, there was a moment (or longer) of hesitation before the ball you clicked on disappeared from the right frame! This is because the statement

    document.images[idx].src="images/noball.gif"; //kill image

    forced your browser to fetch a new image (noball.gif) off the internet. It would have been better had we pre-loaded this image by saying in our header:

       hiddenNoball = new Image();
       hiddenNoball.src = "noball.gif";
    

    This would have forced the browser to pre-load the image, which could then be rapidly pasted in over the original picture of a ball using, say

    document.images[idx].src=hiddenNoball.src

Frames in Detail

We should probably have a bit more on frames here. But, remember that, in general, JavaScript really sees frames as having the same properties as windows. Okay, there are catches - certain things that would work in windows mysteriously don't in frames (for example, onUnload), but generally, if you know windows, you know frames.

Also note that in Netscape Navigator (a pox on them) you cannot dynamically write a <script> to the page - NN scours the text you write for such tags and viciously clips them out. This is really silly. Microsoft actually got it 'write', and allow this freedom!

Summary

Frames can be useful, if carefully and tastefully coded. You now know something of how to do this. You now have the option of:
  1. A short tutorial that puts frames to good use (an aside)

  2. The penultimate page of our tutorial (the main themes continued).

Your choice!


Webpage author jvs@anaesthetist.com Last update: 2000-10-7
Copyright © J van Schalkwyk, 2000