/ Home/ Contact/ Resume/ Portfolio/ Code///
 

 

 

Login to customize this site
and access restricted areas
Username
Password
Create Account
 

A functionally invisible frame...
I use this term because the script takes the original html file and rewrites it. If you look at a site that uses Framed! The correct title and URL are displayed in your browser, and the source includes the full text of the page in the <noframes> tag. When you click on a link in a Framed! site, the page title and URL change, just as if there were no frameset. Viewing the page from a non-frames browser shows the full page as it was originally encoded, with an optional header message in place of the frame. This is what I mean by functionally invisible - the frame appears at the top of the page, visible to the user, but without changing any of the dynamics of the way the site works.

Why do this?
Good question. It started because I wanted to make it very clear that my portfolio cache was just that, a cache and not the live site. Many people don't look at the URL of pages they're visiting and so it's easy to confuse my (most likely outdated) cached sites (eg: http://lorriechurch.cache.ravis.org) with an actual live site.

Sounds Simple...
It does, doesn't it? Read on...

I started by adding a PHP script that ran with every page load, using the php_value auto_prepend_file setting in my apache.conf file. That PHP file then pre-pended a JavaScript popup script to the start of each page, this popup explaining that the site was a cache. But there are drawbacks to this method - many people either disable popups or JavaScript altogether. The newest version of Norton Antivirus actually tries and claim many popups are viri, which is odd.

The obvious solution to these problems was to have a notice plastered on each page. There are several ways to do this, including the GeoCities method of placing a JavaScript layer on the page. I personally don't like this because it covers up content, as well as again requiring JavaScript to be on. It is also not acceptable to use with frames because you can never tell which frame to place the layer in. The only acceptable method, from a design and maximum compatibility point of view, was to go with a frame at the top.

This presents it's own problems though. Frames are disliked by many people (including myself) because, which they often look nice, they destroy many of the useful features of a browser. Bookmarks become almost unusable, since bookmarking a page bookmarks the frameset, not the frame you're looking at. Search engines will link to pages within your frameset, without loading the frameset, and so you get those pages that say "Click on a menu item in the frame to the left" when there is no frame to the left. Page titles get all out of whack because again, you're viewing the title of the frameset and not the frame you're looking at. Lastly there are compatibility issues for browsers that don't support frames (before you scoff, think mobile phones and PDAs). Fortunately I have, in the past, done a great deal of work with frames, and have developed various ways around the shortcomings. One of my more recent sites used many of these ideas to circumvent the problems.

Frameset Problem #1: Bookmarks
Solution: All links open in the _top window space - that is, in the top of the browser. This allows the page URL to refresh. It also requires redrawing the frameset on every page load, but there's really no other way around it. It's a small sacrifice in order to circumvent the problems.

Frameset Problem #2: Search Engines
Solution: Automatically loading the frameset at the top of each page. This solution goes hand in hand with Problem #1, in that each page loads at the _top of the window. But instead of having a separate page for the frameset and another for the frame contents, we have only the frame contents. When the page is requested, our code dynamically creates the frameset, sends it to the browser, which then requests the frame contents. We must, however, exercise caution here in order to prevent infinitely recursive framesets - we only want one frameset and one content frame.

Frameset Problem #3: Page Titles
Solution: This problem can be solved by parsing the requested page and retrieving the title from within the <title> tag. We can then take that title and apply it to the frameset, which will in turn be displayed in the browser.

Frameset Problem #4: Compatibility & Search Engines
Solution: By automatically parsing the page and including much of the code in <noframes> tags, we are able to maintain backward compatibility. Since I hate maintaining two copies of any one thing (in this case the frames content and the noframes content, which should be identical) we need to parse and load this content automatically, so that we only have one version to maintain. Also, since search engines will read the noframes content, they'll be more likely to link to our frameset page.

This works great for single pages! Every link opens in _top, which it would do anyway if there was no frameset. We see the title we should see, and backward compatibility is maintained! And it worked really well for the site I developed for my client. You can see a cached copy here (note the red "this site is a cache" header - which is what this page is all about...

Frames Within Frames
However, we've introduced a whole slew of other problems now. The root of which is this: What happens when we want to add our automatically generated frame to a page that already contains frames? If we follow only the steps above each frameset will contain it's own subframeset. In the case of my cache, each frame will contain a red header saying "this is a cache". Obviously we don't want this - not only is it ugly, but it will no doubt disturb the layout of the page (frames are often made to fit the content they display - if we add more content they won't fit anymore).

Solution! Rewrite any framesets in the page to include a variable that we check for. If that variable exists then we don't load the automatic frameset. If that variable doesn't exist then it means the user has requested this page as a _top page and we are free to add the frame. This is fairly simple to do, in that we look for any <frame> (or <iframe>) tags, find the src="<URL>" attribute within them, and add a variable to the end of the URL So if a page contains a frameset:
          <frame src="hello/there.html">
we need to rewrite it to say:
          <frame src="hello/there.html?frameset=false">
Then when we get any page that has a "frameset=false" in the URL, we don't write a new frameset, and instead simply dump the page out to the browser. (Note that I'm using .html here instead of PHP - that's because my cached pages are all .htm and .html - I've configured Apache to run them as PHP scripts instead of changing all the hundreds of filenames.)

Works great! Until you start clicking around in a frames site that is. And this is where it starts getting complex...

Links, Targets, and Frames
          <a href="hello/there/link.html">
What happens when you click on this link when it's in a frameset? If that link opens in _top, we're fine - the above code works like a charm. What if we click on a link that points to another frame? or to itself (_self)? Our code looks at the URL, says "well, there's no 'frameset=false' in there, so let's draw a frameset." Oops. Now we have the frameset within a frameset problem again. Actually there are several problems here, and they all revolve around targets...

Targets specify for the browser where the link that you just clicked should be opened. By default, links are opened in the frame that the link is in. For pages without framesets this works well because you normally want to open links in the same window. For framesets (say with a menu on the side) this doesn't work so well because what you really want to be doing is opening the link in the main frame - where your content is displayed - and not in the menu frame. To do this you specify a target.

There are three possible ways to determine a target. First the browser looks at the link itself. If the link contains a target then this is used when the user clicks that link. eg:
          <a href="hello/there.html" target="main">
If there is no target specified in the link, then the browser looks to the page's <base> tag. The base tag defines a default target for all links on the page (that don't have their own targets) and is contained in the page header and looks something like:
          <base target="main">
Failing to find a base tag, the browser will open the link in the same frame as the link itself.

Why am I explaining all this? Because we need to understand how targets work if we're going to get our automatic frames working properly - we must teach the script that's generating the frames when to draw a new frameset and when to leave the page alone.

To teach the script when to draw framesets and when not to we therefore need to determine the default target and then go through the page and change links.

To determine the default target we can simply look at the <base target=""> tag for the page, right? Sort of. This works great if there is a base tag (and if it contains a target reference). Many pages don't, especially pages where the link is supposed to open in it's own frame (_self) and pages that simply don't contain frames (_top). First we need to figure out what layer we're on. In other words, if we weren't drawing an automatic frameset around our content would we be on the _top? or are we on a subframe? We can accomplish this by maintaining a depth counter. Instead of adding the "frameset=false" to each frame src (as above) we add "depth=x" where x is incremented from the received value (if any).

What happens is the first page you load (the automatic frameset) creates a frameset with a link to the page content using our depth counter + 1. Since this is the first page we've loaded, our depth counter is missing (and therefore 0). We add one and our frame tag then looks like:
          <frame src="hello/there.html?depth=1">
The browser receives this frameset and loads the specified page (namely hello/there.html?depth=1). Our script looks at the URL, finds our depth counter, and rewrites any subframesets by incrementing that counter. So sub-frames would look like:
          <frame src="hello/there/subframe.html?depth=2">
and so on until all the frames are loaded. This provides us with a simple way of determining the default target tag (which, if you recall, is what we're after). If the depth is 1, then our default target would be _top (without our automatic frame). If it's greater than 1, then our default target is _self (because it would be a subframe, even without our automatic frameset).

Now we have the default target we can start looking for links to change. Basically, any link that doesn't have a target tag of it's own will get assigned the default target tag. From then on it's simply a matter of determining which targets need to be changed and which need to be left alone.

There are four special target tags in html. We've already seen two of them: _top and _self. The two others are _blank (opens the content in a new window) and _parent (opens content in the frame just above the current one). Then there are the other targets - user defined targets. Let's address each of these in turn.

_top
_top targets should never be rewritten because we always want to reload the frameset so that we can refresh our title and URL So anytime we see a link with a target of _top (or a link without a target and a page default target of _top) we simply leave it alone.

_blank
_blank targets never need to be rewritten because the whole idea of opening a link in _blank is to open it in a new window - so we want a frameset at the top of that window. Easy.

_parent
_parent links are a little more difficult. If we're on a depth of 2 or less then _parent actually refers to _top and we actually do want a frameset redrawn, so we don't want to manipulate the link - leave it as is and the script will reload the frameset if the user clicks on it. If, however, we're on a sub-subframe (ie: with a depth of 3 or more) than we want to rewrite the link to include a depth tag, and therefore not redraw our frameset (which would end up being within our frameset, etc). So we change these links to include a depth counter (just like our frames) that will fool our frames script into thinking that this page is being loaded as part of a frameset (which, in fact, it is).

_self
_self targets are also a bit tricky. Like parent targets they can have two different meanings depending on what _self refers to. If we're in a subframe (ie: depth is 2 or more) then self truly does refer to self and we need to rewrite the link so that we don't redraw our frameset. If, however, depth is 1 then _self really refers to _top. In this case we want a frameset redrawn and so we leave the link alone.

user defined targets
User defined targets always get rewritten because they are always opening in sub-frames. The exception to this is when a user defined target is used to open a new window. This complicates the situation tremendously and therefore we'll just ignore it. :-) There's only so much we can do and this practice is fairly rare anyway (most sites these days use JavaScript to open new windows).

Done! Wasn't that easy?

Now we have a script that automatically adds a frameset to every page loaded, avoids many of the problems inherent in framesets, avoids infinite recursion, and avoids loading automatic framesets within framesets. Turns out to be a lot more complex than it first looks. But damn, was that fun or what?

Now that your brain's fried, go watch a good movie and recuperate...

 

 

Udon Thani Webcam:

Webcam image - click to zoom
It's 3:17 on a Friday afternoon in Udon Thani, Thailand...

(zoom image)