Today's Episode:
I've Been Framed!
For those of you who've nosed around elsewhere on this website, you may have noticed the significant portion of it dedicated to Irish Step Dancing. You may have also noticed most of this section's size comes from the reviews of each feis (a Gaelic word for dancing that's pronounced "fesh," before you ask) our family goes to throughout the year. When I originally set them up, I decided to use frames to make it easier to navigate through the reviews subsection.
All decisions have consequences though, and my choice of feis review organization was not exempt from this truism. I started doing capsule summaries of my reviews for a master feis review site, and after getting approval from that site's webmaster included links back to my more sizable review. This resulted in people landing on my more detailed review without getting any of the site navigation aids I had included in my navigation frame. Oops...
Things got worse when we started the 2001 competition season. On various reviews, I found it neccessary to link back to reviews from previous year to help explain whatever point I was trying to make. Clicking on the link loaded a review from last year into this year's frameset -- not neccessarily a catastrophe, but not neccessarily correct, either. Specifying a target of _top for the link caused the review to load without its navigation frame, with consequences as explained above.
Fortunately, what I have described is a well-known problem with HTML frame management. Even more fortunately, there are several solutions to it that can be found at places like WebReference and JavaScriptSource. In general, these solutions are composed of two parts: some Javascript in your content pages and some more Javascript in the page which defines your frames.
Let's first look at the typical code used for a content page. In any solution presented at these sites, we find in the <HEAD> block something that looks like:
<SCRIPT LANGUAGE="Javascript">
if (top.location == self.location)
top.location.href = 'feisanna.html?feis.html'
</SCRIPT>
What this code does in the event the content document gets loaded by itself is slick. Sensing it's not loading within a frame, it resets the href property of the top level's location to the page containing your frameset definition (in this case, feisanna.html) and then appends its own location as the search portion of the new URL (everything after the question mark). This forces your frameset document to load in place of the document originally requested
Now let's look at the second part of the typical solution. This will tend to look something like:
<SCRIPT LANGUAGE="Javascript">
var content_page = location.search? location.search: 'main.html';
document.write('<FRAMESET COLS="160,*">');
document.write(' <FRAME NAME="nav" SRC="nav.html">');
document.write(' <FRAME NAME="content" SRC=' + content_page + '>');
document.write('</FRAMESET>');
</SCRIPT>
The frameset's script first parses the incoming URL to see if there is a portion containing search data. If there is, it then recovers the original target document from the location.search property; if not, it defaults to the content page people would see first if they arrived normally. The document.write() functions then dynamically write the framesets so they are defined with the proper source documents.
The use of Javascript's document.write() function is where I start disliking the standard solution. In my opinion, document.write() for generating custom HTML is used in many situations where it is far easier and simpler to set the property of an existing object in the document. Rather than create the entire frameset from scratch, all you really need to do is:
- write the frameset in static HTML as before, and then...
- set the
location.hrefproperty of the content frame based on the incoming URL.
Others must actually think the same as I do, for this is exactly what the solution presented at JavaScriptSource does. With some modifications I'll explain below, the resulting contribution to the world of Strange Code would thus look something like:
<HEAD>
<TITLE>Frameset Document</TITLE>
<SCRIPT LANGUAGE="Javascript">
function frame_load() {
var request = location.search // cache this -- we'll be using it a lot
var target = request? request.substr(1,request.length): 'main';
parent.content.location.href = target + '.html';
}
</SCRIPT>
</HEAD>
<FRAMESET COLS="160,*" onload="frame_load()">
<FRAME NAME="nav" SRC="nav.html">
<FRAME NAME="content">
</FRAMESET>
frame_load() gets called when the frameset page loads, and first scans the incoming URL for any search strings encoded in the URL -- just like before. After determining the source document for the content frame, its location.href property is set. I've slightly modified the overall technique so that the original document now only passes the filename as part of the URL's search parameter, which makes the code in the frameset document now responsible for making sure the .html file extension gets added to the file name. The only reason I do this is the coolness factor: the resulting URL makes it look like I'm doing something really exotic in the server instead of relying on a five-line Javascript function and an obscure aspect of the HTML 4 specification.
To see this technique in operation, go to either my 2000 Feis Reviews or 2001 Feis Reviews and use your browser's View Source and View Frame Source commands. Try loading an individual review or two to see what happens, and enjoy the Strangeness
UPDATE: This code is no longer used on the Clan Seger website as of 2006
BUGS FEATURES
When you load the content document by itself, resetting top.location.href to load your frameset forces an internal redirect. This means both the original attempt to load the document and the redirect are now part of your browser's history. Hitting the back button will return you to your original attempt to load the document, which will immediately loop you back into the frameset. To counteract this, use the replace property of the location object, implementation of which is left as an exercise for the reader.
As written above, the frameset code will not work for IE5 on a Macintosh. The line in frame_load() that sets the variable "target" has to be replaced with an if...then...else construct to correctly handle setting that variable.