Better, Shareable <iframes> and <framesets>

Saturday, 3rd October 2020

If you're using an <iframe> or <frameset> on your website, this blog post is for you!

<iframe> and <frameset> are a great way to separate your website's layout and menu from its individual pages. Usually you'll have an index.html with an <iframe> or <frameset> on it.

If you're using an <iframe>, your index.html will most likely contain your site's layout, including its banner and main navigation. Then you'll have an <iframe> which loads your individual pages, e.g. home.html, about.html, contact.html and so on.

If you're using a <frameset>, your index.html will most likely contain a frameset with frames for the banner, menu, footer and a frame for your actual pages, e.g. home.html. about.html, contact.html and so on.

The Problem

In both cases, it works perfectly well, but there are a couple of limitations.

Limitation #1 - No shareable URLs: By default, you're probably pointing your "page" frame to something like home.html or default.html. Now, when your visitor clicks one of your menu items, the frame updates, but the address bar does not. This means that the visitor has no way of sharing your page, or bookmarking it to come back later. It also means that search engines like Google have no way of indexing the page!

Limitation #2 - If the visitor does somehow visit one of your pages directly, e.g. instead of clicking the menu item in your layout, they go directly to home.html, your visitor will only see the contents of home.html and they won't see the rest of your layout! Usually webmasters deal with this by having a link to the main site. But then the visitor must navigate back to the frame page again.

The following demos show using iframes and framesets in this manner, check them out:

The Solution

Let's see what we can do about it! We have two goals:

Goal #1 - Create shareable URLs. When the user visits the page, the correct content page is loaded into the frame automatically based on a URL parameter.

Goal #2 - If the visitor does somehow visit one of the content pages directly, have them automatically redirected to the appropriate URL we created in goal #1.

The rest of the tutorial will guide you through using JavaScript to achieve this two goals. If you don't care how it works, you can skip to the end where you can download the completed script and implement it with just a few lines of code!

In either case, you may want to look at the finished demos at this point to see if what we're doing in this blog post is what you're looking for:

#1 Shareable URLs

Okay. So let's say we have the following code in our index.html.

<frameset cols="20%, 80%">
  <frame name="menu-page" src="menu.html">
  <frame name="content-page" src="home.html">
</frameset>

As you can see, this is a simple frameset with two columns. The menu-page frame always points to an html file with the site's title and navigation. The content-page points to one of our content pages, defaulting to home.html. But the user might navigate to about.html or home.html. This is similar to the Standard <frameset> Demo.

Similarly, if you're using an iframe instead, you might have the following in your index.html:

<div class="menu">
  <a href="home.html" target="content-frame">Home</a> |
  <a href="about.html" target="content-frame">About</a> |
  <a href="contact.html" target="content-frame">Contact</a>
</div>
<div class="content">
  <iframe name="content-frame"></iframe>
</div>

Here, the iframe is the content page loading frame for home.html, about.html and contact.html and updates when you click one of the links in the menu div. This is similar to the Standard <iframe> Demo.

First thing's first, we need JavaScript to achieve our goals. So open up the index.html that contains your iframe or frameset and update the <head> section with something like the following:

<head>
  ..
  <script>
    // We will code here!
  </script>
</head>

So here's when the fun begins. What we want to do is grab a URL parameter, and use that to tell the content-page frame what to load. So for example, if the user visits mysite.com?page=about.html, page is the URL parameter, and its value is about.html.

Unfortunately JavaScript doesn't have a built in function to get a URL parameter, so we need to make one. This is a tad outside the point of this tutorial, so just this once I'm not going to explain this one in detail. Instead I'm just gonna plonk it here for you to copy and paste. But basically it looks at the URL and extracts the variable we need as described in the previous paragraph:

function urlParameter( key )
{
  var queryStr = window.location.search;
  var findStr = "?" + key + "=";
  var startIdx = queryStr.indexOf( findStr );
  
  if( startIdx == -1 )
  {
    findStr = "&" + key + "=";
    startIdx = queryStr.indexOf( findStr );
  }
  
  if( startIdx == -1 ) return "";
  
  var endIdx = queryStr.indexOf( "&", startIdx );
  
  if( endIdx == -1 ) endIdx = queryStr.length;
  
  return queryStr.substring( startIdx + findStr.length, endIdx );
}

Alright nice! Go ahead and paste that into the script section you created in the head section of your HTML document.

Now that we have this function, we're going to use it to get the page parameter from the URL and store it. Personally I like all my code to be inside a function, and since this code is going to be called from the index.html which contains the frameset or iframe, I'm going to call the function initIndexPage() and call it. So the new code will look like this:

// Make a variable to store the requested page
var requestPage = "";

function initIndexPage()
{
  // See which page the visitor wants
  requestPage = urlParameter( "page" );
}

function urlParameter( key )
{
  ...
}

initIndexPage();

Okay great! The requestPage variable now stores which content page to get the frame to load. The thing is though, we don't want the visitor to be able to request any old page. What if they typed mysite.com?page=https://www.google.com. Google would be loaded into your own website's frame! And we don't want that, so let's make a safe list.

var requestPage = "";

// Set safe pages which can be loaded into the content page
var safePages =
[
  "home.html",
  "about.html",
  "contact.html"
]

function initIndexPage()
{
  ...
}

function urlParameter( key )
{
  ...
}

Now we just need to update the initIndexPage() function to use the safe list. If the page in the URL parameter isn't in the list, the home page is used.

While we're at it, let's make it so if the page parameter isn't set in the URL at all, it uses the home page. That way the visitor doesn't have to visit ?page=home.html to get the home page.

function initIndexPage()
{
  // See which page the visitor wants
  requestPage = urlParameter( "page" );
  
  // If they didn't request any page, use the home page
  if( requestPage == "" ) requestPage = "home.html";
  // If the page they want isn't in the list, use the home page
  if( safePages.indexOf( requestPage ) == -1 ) requestPage = "home.html";
}

Okay we're all set to actually tell the frame which page to load! To do this, we need to replace the iframe or frameset code with some JavaScript which uses our variable.

But it's nice to use functions for this stuff, so let's make a function. If you're using a frameset, it will look something like the following:

// This function will replace your frameset code
function writeFrameHTML()
{
  var frameHTML = `
    <frameset cols="20%, 80%">
      <frame name="menu-page" src="menu.html">
      <frame name="content-page" src="` + requestPage + `">
    </frameset>
  `;
  
  document.write( frameHTML );
}

If you're using an iframe, it will look something like the following:

// This function will replace your frameset code
function writeFrameHTML()
{
  var frameHTML = '<iframe name="content-page" src="' + requestPage + '">';
  
  document.write( frameHTML );
}

That's ALL of the JavaScript for index.html! We will need to call the writeFrameHTML() function in place of our existing frameset or iframe code, but let's take a quick look at the complete JavaScript so far (using the frameset as an example)...

Finished index.html JavaScript

// Variable for storing which page the visitor wants
var requestPage = "";

// Set safe pages which can be loaded into the content page
var safePages =
[
  "home.html",
  "about.html",
  "contact.html"
];

// Work out which page the visitor wants, make sure it's safe
function initIndexPage()
{
  // See which page the visitor wants
  requestPage = urlParameter( "page" );
  
  // If they didn't request any page, use the home page
  if( requestPage == "" ) requestPage = "home.html";
  // If the page they want isn't in the list, use the home page
  if( safePages.indexOf( requestPage ) == -1 ) requestPage = "home.html";
}

// This function will replace your frameset code
function writeFrameHTML()
{
  var frameHTML = `
    <frameset cols="20%, 80%">
      <frame name="menu-page" src="menu.html">
      <frame name="content-page" src="` + requestPage + `">
    </frameset>
  `;
  
  document.write( frameHTML );
}

// Helper function for getting a variable value from the URL
function urlParameter( key )
{
  var queryStr = window.location.search;
  var findStr = "?" + key + "=";
  var startIdx = queryStr.indexOf( findStr );
  
  if( startIdx == -1 )
  {
    findStr = "&" + key + "=";
    startIdx = queryStr.indexOf( findStr );
  }
  
  if( startIdx == -1 ) return "";
  
  var endIdx = queryStr.indexOf( "&", startIdx );
  
  if( endIdx == -1 ) endIdx = queryStr.length;
  
  return queryStr.substring( startIdx + findStr.length, endIdx );
}

// Call the init function xD!
initIndexPage();

All we need to do now in index.html is swap our frameset or iframe HTML for our call to writeFrameHTML(). So go and find your equivalent to this in your HTML:

<frameset cols="20%, 80%">
  <frame name="menu-page" src="menu.html">
  <frame name="content-page" src="home.html">
</frameset>

And swap it for this!

<script>writeFrameHTML();</script>

Now when you visit yoursite.com?page=home.html, or yoursite.com?page=about.html, etc, the correct page will be loaded into the frame. You can share that URL with potential visitors, your visitors can bookmark it, and it will be indexed by Google. Nice!

But we're not quite finished just yet. We need to update our navigation to actually USE these new URLS! If you're using a frameset, you need to update your menu.html equivalent. It will probably look something like the following:

<div class="menu">
  <a target="content-page" href="home.html" >Home</a> |
  <a target="content-page" href="about.html">About</a> |
  <a target="content-page" href="contact.html">Contact</a>
</div>

You'll need to change the target and href attributes so that they look like the following:

<div class="menu">
  <a target="_top" href="./?page=home.html" >Home</a> |
  <a target="_top" href="./?page=about.html">About</a> |
  <a target="_top" href="./?contact.html">Contact</a>
</div>

What this does is says when the link is clicked, change the TOP LEVEL url to the one in the href.

If you're using an iframe, it's the same, but you can drop the target attibute completely and just use the href, since the links in index.html are already in the top level document!

That's it for index.html, and the system fully works. We could stop at this point and most visitors would be happy. But let's sort out goal #2.

Goal #2 - Redirecting if Land on Content Page

So, the other issue is that if the visitor visits one of the content pages directly, they'll be able to see it, but they won't see your overall layout and menu. Luckily, this is nice and easy to fix. We just need a function which grabs the current URL and redirects to the "new" equivalent URL we made in Goal #1. It's a single function, and it looks like follows...

function initContentPage()
{
  // If a visitor has reached this page directly
  if( window.self == window.top )
  {
    // Work out which page I am
    var path = window.location.pathname;
    var page = path.split("/").pop();
    // Redirect to equivalent better URL
    window.location = "./?page=" + page;
}

initContentPage();

Go ahead and put that in all of your content pages, and the redirect will happen automatically!

The End - Finishing Up & Download

Alright, if you've made it this far you've either skipped to the end because you want the download, or you've just read the whole thing. Either way, I'm now going to give you a download which contains the complete script and all the demos. Please be aware the enhanced demos won't work properly on your local file system. You need to either install a local web development server, or upload it to a web host.

The download also puts all of the JavaScript into a single file, meaning all we have to do is edit the config section in it, include it in our index and content pages, and call a couple of functions!

Once you have the download, put it somewhere where you know where it is inside your website's files. Open it up, and go to the config section. Then, on your index.html page just include it in the head section and, call the initIndexPage() function.

<script src="better-frames.js"></script>
<script>initIndexPage();</script>

Replace your existing frameset or iframe code with a call to the writeFrameHTML() function:

<script>writeFrameHTML();</script>

And finally, load the same script and pop a call to initContentPage(); in each of your content pages.

<script src="better-frames.js"></script>
<script>initContentPage();</script>

I hope someone out there finds this tutorial/download useful! If it helped you or you need some help, be sure to leave a comment below!

 
Sckewi's Site