Whenever one builds a website, one issue is always guaranteed time consuming: highlighting the current page or section in the website with a different style.
In theory, this is relatively simple, just add a .current class to the link in question.
So, if you have a menu styled from a list, you'd have:
HTML:
<ul> | |
<li><a href="#">Link 1</a></li> | |
<li><a href="#" class=".current">Link 2</a></li> | |
<li><a href="#">Link 3</a></li> | |
<li><a href="#">Link 4</a></li> | |
</ul> |
The limitation of this method becomes obvious if you have a site with more than 5-7 pages; each page needs to have its own menu, which makes updates difficult, especially if you prefer to keep the menu in a template.
I've seen several solutions to this problem, but I didn't really like any of them - so I propose something more clever.
Follow up:
One possibility, used by A List Apart would be to assign an id to each link, so that the links above become:
HTML:
<ul> | |
<li><a id="link1" href="#">Link 1</a></li> | |
<li><a id="link2" href="#" class=".current">Link 2</a></li> | |
<li><a id="link3" href="#">Link 3</a></li> | |
<li><a id="link4" href="#">Link 4</a></li> | |
</ul> |
Then, you'd have to set the class property for each <body> tag, i.e. <body class="link1">. In the stylesheet definition, you'd write
CSS:
.link1 a#link1, | |
.link2 a#link2 | |
{ | |
background-color: #EEEEEE; | |
| |
} |
If you're relatively new to CSS, the above definition means that any link with id set to link1 that is a descendent of a tag (<body>) with the class .link1 will be "highlighted".
This method is an improvement in that the menu can be written only once; however, you still have to set the class for the body tag in each page.
Another solution would be to use PHP/ASPX or any other server language to generate the pages and menu items. Of course, this provides complete freedom, since you can generate the html code any way you like. Still, this method can't be used in all circumstances. Not all sites require a server technology and making a site dynamic just for this feature would be overkill.
I'm a little surprised that I haven't seen a similar solution published anywhere, so I've thought about sharing it.
The concept is very simple: The javascript code gets the current URL. It then cycles to all links that are contained in the navigation bar (any tag with id="navbar"). If the link points to the same page (i.e. "link1.html"), the code applies a class to the link.
The code should work in all browsers that support getElementById() including IE5, Firefox & Opera. You may add compatibility for older versions of IE by using document.all in addition to document.getElementById()
Here's the code (rewritten on Oct.11.2007):
Java:
function extractPageName(hrefString) | |
{ | |
var arr = hrefString.split('/'); | |
return (arr.length<2) ? hrefString : arr[arr.length-2].toLowerCase() + arr[arr.length-1].toLowerCase(); | |
} | |
| |
function setActiveMenu(arr, crtPage) | |
{ | |
for (var i=0; i<arr.length; i++) | |
{ | |
if(extractPageName(arr[i].href) == crtPage) | |
{ | |
if (arr[i].parentNode.tagName != "DIV") | |
{ | |
arr[i].className = "current"; | |
arr[i].parentNode.className = "current"; | |
} | |
} | |
} | |
} | |
| |
function setPage() | |
{ | |
hrefString = document.location.href ? document.location.href : document.location; | |
| |
if (document.getElementById("nav")!=null) | |
setActiveMenu(document.getElementById("nav").getElementsByTagName("a"), extractPageName(hrefString)); | |
} |
The ony thing left to do is to call setPage() from the html page, just after the menu, with <script language="javascript">setPage()< /script>
You can make the script run without explicitly calling setPage() from the html body, by adding
Java:
window.onload=function() | |
{ | |
setPage(); | |
} |
in the .js file (Suggested by Alice). If you go this route, please test to make sure that it doesn't interefere with other onLoad events. Also, in this case the effect will be applied only after all page elements have been loaded.
That's all. You can download a fully working example.
Geoff, you can have the highlighter work simultaneously on different navigation menus, but only if they point to the same page, i.e. you have a top navigation with a link to stuff.html and a left-side navigation also pointing to stuff.html
In this case, in the setPage() function, duplicate the last line for the new div:
if (document.getElementById('navbar')!=null)
setActiveMenu(document.getElementById('navbar').getElementsByTagName('a').extractPageName(hrefString));
if (document.getElementById('navleft')!=null)
setActiveMenu(document.getElementById('navleft').getElementsByTagName('a').extractPageName(hrefString));
}
It can get however a little confusing, especially if you have links to documents with the same name, e.g. index.htm but placed in different folders, e.g. /work/index.htm and /home/index.htm. To cope with this, I rewrote the extractPageName() function but I want to test it more before i publish it.
ul#menu>li>ul#submenu>li>aarr[i].className = 'current';.'a' tag) but you can also apply it (or some other class) to one of its ancestors.arr[i].parentNode.parentNode.parentNode.className = 'current'; to apply the 'current' class to the 'li' that is part of the 'ul#menu' list.blah.com/page.htm#end), the same as query strings do. Here's a simple fix I wrote to address both in only 2 lines of code, plus slightly altering one of your existing lines of code:
function extractPageName(hrefString)
{
var hrefString = hrefString.split('#');
var hrefString = hrefString[0].split('?');
var arr = hrefString[0].split('/');
return (arr.length<2) ? hrefString : arr[arr.length-2].toLowerCase() + arr[arr.length-1].toLowerCase();
}