A URL-driven tabbing system: tutorial

Tab 1:
Intro&HTML
Tab 2:
the script
Tab 3:
external: jQuery
Tab 4:
jQuery demo
Tab 5:
external: iframe
Tab 6:
iframe demo
Tab 7:
'tabs' inside panels
Tab 8:
basic source code
This is a URL-driven tabbing system. It uses the browser URL and the hashchange event on the window to drive it. The native browser back/forward button correctly changes the state of the selected tab (link), so the different panels ('pages') can be bookmarked.

The HTML:
We need an area where we can put the panel-content of our tabbing system (you may change the CSS, but not the ID; CSS kept inline for readability):
<div style="position: relative; margin-top: 20px; padding: 50px; padding-top: 20px; border: 1px solid black">
<div id="panel_area" style="position: relative; width: 100%; height: auto">
</div>
</div>

These lines must be preceded (preferably) by the tabs (the links) used for displaying the panels in the panel area. Here are two tabs that let you display (hidden) content from the page in the panel area. THE HREF VALUES FOR THE TABS MUST START WITH A HASH-TAG:
<a class="display_panel" href="#first_panel">Tab 1</a>
<a class="display_panel" href="#second_panel">Tab 2</a>
The class name on these tabs will be used by the tabbing system for highlighting the tabs corresponding to currently displayed panels. If you remove it, no damage will be done except that the highlighting won't work anymore. The href values for the tabs MUST correspond exactly (except for the hash symbol in the hrefs) to the names for the IDs on the hidden divs below (anywhere in the body), whose contents the system is supposed to put in the panel area when the tabs are clicked on:
<div style="position: absolute; display: none; ">
<div id="first_panel">
This is tab content 1<br>bla bla
</div>
<div id="second_panel">
This is tab content 2<br>bla bla
</div>
</div>
Notice that you are free to change the href value in a tab. If you do so, make sure that the ID on the hidden div containing 'tab content' is modified accordingly. For instance, if you replace the href value in the first tab above with #content_of_first_tab, then put id="content_of_first_tab" for the first hidden div.

Here's what we have so far:
<a class="display_panel" href="#first_panel">Tab 1</a>
<a class="display_panel" href="#second_panel">Tab 2</a>

<div style="position: relative; margin-top: 20px; padding: 50px; padding-top: 20px; border: 1px solid black">
<div id="panel_area" style="position: relative; width: 100%; height: auto">
</div>
</div>

<div style="position: absolute; display: none; ">
<div id="first_panel">
This is tab content 1<br>bla bla
</div>
<div id="second_panel">
This is tab content 2<br>bla bla
</div>
</div>

Now let's take a look at the script that makes this work (click on the second tab at the top of this page).
TAB 2: the script
The SCRIPT (try to understand it; if you cannot, just use it):
In the head (replace jQuery with a more recent version if needed):
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>

<script>
function hash_change()
{

/* FILLING THE PANEL-AREA WITH CONTENT FROM A HIDDEN DIV. FOR SOME REASON, NON-JQUERY INNERHTML DOES NOT ALWAYS YIELD THE EXPECTED RESULTS, SO WE USE JQUERY-INNERHTML, WHICH WORKS WELL
document.getElementById('panel_area').innerHTML = document.getElementById(location.hash.substring(1,location.hash.length)).innerHTML*/
$('#panel_area').html(document.getElementById(location.hash.substring(1,location.hash.length)).innerHTML)

/* HIGHLIGHTING THE SELECTED 'TAB' */
var x = document.getElementsByClassName("display_panel");
var i;
for (i = 0; i < x.length; i++)
{
x[i].removeAttribute('style');
if(x[i].href.substring(x[i].href.lastIndexOf('#')+1, x[i].href.length) == location.hash.substring(1,location.hash.length))
{/* REPLACE THIS WITH YOUR OWN CSS */
x[i].style.borderBottom='2px solid black'
}
}

}
window.onhashchange=hash_change;
</script>
At the end of the body section:
<script>
/* Load the first 'panel' at page entrance */
if(window.location.hash==''){window.location.replace('#first_panel');}

hash_change()
</script>
TAB 3: loading external documents in the content tab - the jQuery method
Suppose we don't want a tab to display something like:
This is tab content 3
bla bla

as produced by the following lines:
<a class="display_panel" href="#third_panel">Tab 3</a>

<div style="position: absolute; display: none; ">
<div id="third_panel">
This is tab content 3<br>bla bla
</div>
</div>
but rather content from an external document (same domain), for instance: your_external_document.html. JQuery makes this very easy. Instead of the above lines for the div, we write (jQuery method for loading external content belonging to your domain):
<div style="position: absolute; display: none; ">
<div id="third_panel">
<div id="external_content" ></div>
<script>$('#external_content').load('your_external_document.html')</script>
</div>
</div>
Of course, we must make sure that the ID referenced by the jQuery method (here: external_content) is not used a second time for another external document. Click on tab 4 for a demo.
TAB 5: including external content using an iframe
If you just want to include an iframe (loading external content) without feeling the need to integrate it seamlessly with the rest of the page, you can simply do something like:
<a class="display_panel" href="#fifth_panel">Tab 5</a>

<div style="position: absolute; display: none; ">
<div id="fifth_panel">
<iframe src="your_external_document.html" style="..."></iframe>
</div>
</div>
But if you want the iframe to seamlessly integrate with your main document, you must do this:
<a class="display_panel" href="#fifth_panel">Tab 5</a>

<div style="position: absolute; display: none; ">
<div id="fifth_panel">
<iframe frameborder="0" scrolling="no" src="about:blank" onload="seamless_iframe(this, 'your_external_document.html')"></iframe>
</div>
</div>
and put the script that does the 'seamless job' in the head of the (main) page:
<script>
function seamless_iframe(the_iframe, the_file)
{
for (var i = 0; i < document.getElementsByTagName('style').length; i++)
{
the_iframe.contentWindow.document.getElementsByTagName('head')[0].appendChild(document.getElementsByTagName('style')[i].cloneNode(true))
};
the_iframe.style.width=the_iframe.parentNode.clientWidth+'px';
the_iframe.style.height=0;
the_iframe.style.borderWidth=0;
the_iframe.scrolling='no';
the_iframe.style.verticalAlign='top';
the_iframe.style.padding=0;
the_iframe.style.margin=0;
the_iframe.style.height=Math.max(the_iframe.contentWindow.document.body.scrollHeight, the_iframe.contentWindow.document.body.offsetHeight, the_iframe.contentWindow.document.documentElement.clientHeight, the_iframe.contentWindow.document.documentElement.scrollHeight, the_iframe.contentWindow.document.documentElement.offsetHeight) -5 +'px';
if(the_iframe.contentWindow.location.href=='about:blank'){the_iframe.contentWindow.location.href=the_file};
}
</script>
Also, in order for the code to continue to function properly when the window is resized, put this script at the end of the body section of the (main) document:
<script>
var windowWidth = window.innerWidth;
window.onresize=function()
{
if(windowWidth != window.innerWidth)
setTimeout("location.reload()",10); return
}
</script>

Click on tab 6 (top of page) for the demo.
Text before iframe. If you think the seamless integration of the iframe is not seamless enough yet, wrap the iframe in a div that you give certain values for padding, margin, left position, top position etc. to correct this. See also panel 5 (click on tab 5).
Text after iframe
TAB 7: 'tabs' inside panels
Links in a regular website can be found anywhere on the page, not just in the navigation menu. How about links in a tabbing system like this one? Answer: although the term 'tabs' is normally reserved to the items at the top of the panels, workable links are not reserved to any particular place, at least, not in our tabbing system. In our system, the hash part of the URL in the address bar is always identical to the ID of a hidden div. So the script can use the hash part of a URL as an identifier for content to be written in the panel. And as URLS with a hash symbol can be written anywhere on the page, tab-links can also be written inside panels.

They don't look exactly the same, though. We have seen that tabs in the tab-menu look like this:
<a class="display_panel" href="#first_panel">Tab 1</a>
We've said (first panel) that the class was needed for highlighting the tabs (in the tab-menu) corresponding to currently displayed panels. As we don't need highlighting for links in panels, we can leave out the class, meaning that links inside panels simply look like this:
<a href="#first_panel" >Panel 1</a>. Demo here.
In fact, in our tabbing system we can do everything we are used to on regular websites:
<a href="javascript: void(0)" onclick="location.href='#first_panel'; return false" >First panel</a>. Demo here.
<a href="javascript: void(0)" onclick="location.replace('#second_panel'); return false" >Second panel</a>. Demo here.
etc. So we might consider to create whole websites having panels, not regular pages. But that's up to anyone's personal preferences.
Here's the source of a (basic) URL-driven tabbing system.
Copy the code, paste it into a document, save it, open it and see what happens. The code references a file called your_external_document.html, so make sure to create a file with this name. Put lots of text in it to (better) see how the code correctly handles the embedding of external files.
<!doctype html>
<html >
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script>
function hash_change()
{

/* FILLING THE PANEL-AREA WITH CONTENT FROM A HIDDEN DIV. FOR SOME REASON, NON-JQUERY INNERHTML DOES NOT ALWAYS YIELD THE EXPECTED RESULTS, SO WE USE JQUERY-INNERHTML, WHICH WORKS WELL
document.getElementById('panel_area').innerHTML = document.getElementById(location.hash.substring(1,location.hash.length)).innerHTML*/
$('#panel_area').html(document.getElementById(location.hash.substring(1,location.hash.length)).innerHTML)

/* HIGHLIGHTING THE SELECTED 'TAB' */
var x = document.getElementsByClassName("display_panel");
var i;
for (i = 0; i < x.length; i++)
{
x[i].removeAttribute('style');
if(x[i].href.substring(x[i].href.lastIndexOf('#')+1, x[i].href.length) == location.hash.substring(1,location.hash.length))
{
x[i].style.borderBottom='2px solid black'
}
}

}
window.onhashchange=hash_change;
</script>

<title>Tabbing System</title>

<style>
body {font-family: verdana; font-size: 16px; line-height: 25px; margin:0; padding:0}
.display_panel {margin-left: 5px; margin-right: 5px; }
a:link {text-decoration: none; color: black}
a:visited {color: black}
</style>

<script>
function seamless_iframe(the_iframe, the_file)
{
for (var i = 0; i < document.getElementsByTagName('style').length; i++)
{
the_iframe.contentWindow.document.getElementsByTagName('head')[0].appendChild(document.getElementsByTagName('style')[i].cloneNode(true))
};
the_iframe.style.width=the_iframe.parentNode.clientWidth+'px';
the_iframe.style.height=0;
the_iframe.style.borderWidth=0;
the_iframe.scrolling='no';
the_iframe.style.verticalAlign='top';
the_iframe.style.padding=0;
the_iframe.style.margin=0;
the_iframe.style.height = Math.max(the_iframe.contentWindow.document.body.scrollHeight, the_iframe.contentWindow.document.body.offsetHeight, the_iframe.contentWindow.document.documentElement.clientHeight, the_iframe.contentWindow.document.documentElement.scrollHeight, the_iframe.contentWindow.document.documentElement.offsetHeight) -5 +'px';
if(the_iframe.contentWindow.location.href=='about:blank'){the_iframe.contentWindow.location.href=the_file};
}
</script>

</head>
<body>

<div style="text-align: center; margin-top: 30px; margin-bottom: 40px">
<a class="display_panel" href="#first_panel" >First panel</a>
<a class="display_panel" href="#second_panel" >Second panel</a>
<a class="display_panel" href="#third_panel" >Third panel</a>
<a class="display_panel" href="#fourth_panel" >Fourth panel</a>
</div>

<div style="position: relative; margin-top: 20px; padding: 50px; padding-top: 20px; border: 1px solid black">
<div id="panel_area" style="position: relative; width: 100%; height: auto">
</div>
</div>

<div style="position: absolute; display: none; ">

<!-- FIRST PANEL -->
<div id="first_panel">
Just some text ('internal').<br> 1 Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur. 2 Hi omnes lingua, institutis, legibus inter se differunt. Gallos ab Aquitanis Garumna flumen, a Belgis Matrona et Sequana dividit. 3 Horum omnium fortissimi sunt Belgae, propterea quod a cultu atque humanitate provinciae longissime absunt, minimeque ad eos mercatores saepe commeant atque ea quae ad effeminandos animos pertinent important, 4 proximique sunt Germanis, qui trans Rhenum incolunt, quibuscum continenter bellum gerunt. Qua de causa Helvetii quoque reliquos Gallos virtute praecedunt, quod fere cotidianis proeliis cum Germanis contendunt, cum aut suis finibus eos prohibent aut ipsi in eorum finibus bellum gerunt. 5 Eorum una pars, quam Gallos obtinere dictum est, initium capit a flumine Rhodano, continetur Garumna flumine, Oceano, finibus Belgarum, attingit etiam ab Sequanis et Helvetiis flumen Rhenum, vergit ad septentriones. 6 Belgae ab extremis Galliae finibus oriuntur, pertinent ad inferiorem partem fluminis Rheni, spectant in septentrionem et orientem solem. 7 Aquitania a Garumna flumine ad Pyrenaeos montes et eam partem Oceani quae est ad Hispaniam pertinet; spectat inter occasum solis et septentriones. </div>

<!-- SECOND PANEL -->
<div id="second_panel">
External content via jQuery
<hr style="border-color: silver; border-top:0">
<div id="external_content" ></div>
<script>$('#external_content').load('your_external_document.html')</script>
</div>

<!-- THIRD PANEL -->
<div id="third_panel">
External content via a seamless iframe
<hr style="border-color: silver; border-top:0">
<iframe frameborder="0" scrolling="no" src="about:blank" onload="seamless_iframe(this, 'your_external_document.html')"></iframe>
</div>

<!-- FOURTH PANEL -->
<div id="fourth_panel">
'TABS' inside a panel.<br>
1 Gallia est omnis divisa in partes tres, quarum unam incolunt Belgae, aliam Aquitani, tertiam qui ipsorum lingua Celtae, nostra Galli appellantur. 2 <a href="#first_panel" style="color: red; text-decoration: underline">display first panel</a> Hi omnes lingua, institutis, legibus inter se differunt. Gallos ab Aquitanis Garumna flumen, a Belgis Matrona et Sequana dividit. 3 <a style="color: red; text-decoration: underline; cursor: pointer" href="javascript: void(0)" onclick="window.open('#second_panel'); return false">open second panel in new tab</a> Horum omnium fortissimi sunt Belgae, propterea quod a cultu atque humanitate provinciae longissime absunt, minimeque ad eos mercatores saepe commeant atque ea quae ad effeminandos animos pertinent important, 4 proximique sunt Germanis, qui trans Rhenum incolunt, quibuscum continenter bellum gerunt. Qua de causa Helvetii quoque reliquos Gallos virtute praecedunt, quod fere cotidianis proeliis cum Germanis contendunt, cum aut suis finibus eos prohibent aut ipsi in eorum finibus bellum gerunt. 5 Eorum una pars, quam Gallos obtinere dictum est, initium capit a flumine Rhodano, continetur Garumna flumine, Oceano, finibus Belgarum, attingit etiam ab Sequanis et Helvetiis flumen Rhenum, vergit ad septentriones. 6 Belgae ab extremis Galliae finibus oriuntur, pertinent ad inferiorem partem fluminis Rheni, spectant in septentrionem et orientem solem. 7 Aquitania a Garumna flumine ad Pyrenaeos montes et eam partem Oceani quae est ad Hispaniam pertinet; spectat inter occasum solis et septentriones.
</div>

</div>

<script>
/* Load the first 'panel' at page entrance */
if(window.location.hash==''){window.location.replace('#first_panel');}

hash_change()
</script>

<script>
var windowWidth = window.innerWidth;
window.onresize=function()
{
if(windowWidth != window.innerWidth)
setTimeout("location.reload()",10); return
}
</script>

</body>
</html>