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
- Make a set of frames (left and right);
- 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.
- The document in the LEFT frame accepts the information provided,
and draws the stated balls.
- 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:
- A
short tutorial that puts frames to good use (an aside)
- The
penultimate page of our tutorial (the main themes continued).
Your choice!