Automatically highlight current page in menu via Javascript

by Armand Niculescu Email
  • Currently 2.90/5
  • 1
  • 2
  • 3
  • 4
  • 5
  • i

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=&quot;#&quot;>Link 1</a></li>
  <li><a href=&quot;#&quot; class=&quot;.current&quot;>Link 2</a></li>
  <li><a href=&quot;#&quot;>Link 3</a></li>
  <li><a href=&quot;#&quot;>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:

A pure CSS solution

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=&quot;link1&quot; href=&quot;#&quot;>Link 1</a></li>
  <li><a id=&quot;link2&quot; href=&quot;#&quot; class=&quot;.current&quot;>Link 2</a></li>
  <li><a id=&quot;link3&quot; href=&quot;#&quot;>Link 3</a></li>
  <li><a id=&quot;link4&quot; href=&quot;#&quot;>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.

Generating HTML

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.

Javascript + CSS

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.

29 comments

Comment from: Richard [Visitor]
I've used similar techniques on web pages.

Just add window.onload=setPage; to your javascript and it should trigger automatically.
14 Dec 05 @ 17:27
Comment from: Armand Niculescu [Member] Email · http://www.media-division.com
You're correct Richard; I choosed to call the setPage function from HTML so that it can execute after the navigation is loaded but before the wholepage is fully loaded, which may be useful for pages with large images.
14 Dec 05 @ 17:36
Comment from: Rob [Visitor]
Thanks for this brilliant and very useful code - I've been looking for this for weeks - a great Christmas present!

This is a brilliant tool to remote-control ssi variable menu content (for example in Contribute site menus). Allows the contributer to add any menu items using a simple list, yet they still highlight automatically - sweet!

One extra functionality would be to switch off the current link somehow - any ideas?
19 Dec 05 @ 19:15
Comment from: Carrot [Visitor] · http://www.roadlinklogistics.co.uk
This is just what I needed. As mentioned earlier, is there a way to remove the hyperlink from the current page.

Also, I am looking at using this via SSI, and calling the script from a .js using
01 Jun 06 @ 00:52
Comment from: Armand Niculescu [Member] Email · http://www.media-division.com
There should be a way to remove the current page link. I will try to find some time to do this, but no promises.
01 Jun 06 @ 08:04
Comment from: Ben Dalziel [Visitor] · http://bendalziel.blogspot.com
Thanks to Richard for his suggestion that ensures my pages load without errors in the absence of JS.

Absolutely love this code; it's saving me so much time!

I think I found a little bug though and suggest a fix here: http://bendalziel.blogspot.com/2006/07/auto-highlighting-navigation.html

I'm pretty new to JavaScript so any hints or corrections would be great. I return "x" just because I'm not sure what would be better to return...

Thanks again
25 Jul 06 @ 12:28
Comment from: Armand Niculescu [Member] Email · http://www.media-division.com
Thank you for your contribution, Ben!
25 Jul 06 @ 12:32
Comment from: Geoff [Visitor]
Thanks for the code. It's exacly what I was looking for. One question. How would I be able to have multiple links selected on a page, ex. have a sub menu item highlighted? Each of these would be located in different divs. Thanks
26 Jul 06 @ 23:32
Comment from: Armand Niculescu [Member] Email · http://www.media-division.com

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.

27 Jul 06 @ 08:18
Comment from: PDawg [Visitor] Email · http://www.waveactive.com
I've used this script on a previous project. Very cool and useful script. One question that I have is how do I get a main menu item to remain highlighted with I click on a sub-menu item? So, for example, "products" is the main menu item and "iPods", "Cameras", and "TVs" are my sub-menu items. What I would like to see is for "products" to remain highlighted while I click on "cameras".
14 Mar 07 @ 01:05
Comment from: Ahmed G [Visitor]
Great script thanks! And if some of the pages in your navigation have query strings, then substitute the extractPageName function with:

function extractPageName(hrefString)
{
var arr = hrefString.split('.');
// Check to see if the url has a query string by searching for the question mark
var hasquestion=hrefString.indexOf('?');
if (hasquestion == '-1')
{
arr = arr[arr.length-2].split('/');
return arr[arr.length-1].toLowerCase();
}
else
{
return arr[arr.length-1].toLowerCase();
}
}

This will allow your links like filename.php?querystring=blabla to be highlighted as well. Well, it works for me anyway.

Cheers
22 Mar 07 @ 23:34
Comment from: Armand Niculescu [Member] Email · http://www.media-division.com
@PDawg,

You can highlight a parent's link if the menus are nested lists (like http://www.htmldog.com/articles/suckerfish/example/), but I can't give you very specific directions as I don't know your exact configuration.

Anyway suppose you have a hierarchy like
ul#menu>li>ul#submenu>li>a

If you look in the javascript code, you'll see a line
arr[i].className = 'current';.

From there, you apply the 'current' class to the link itself (the 'a' tag) but you can also apply it (or some other class) to one of its ancestors.

You can write arr[i].parentNode.parentNode.parentNode.className = 'current'; to apply the 'current' class to the 'li' that is part of the 'ul#menu' list.

I hope it makes sense...
26 Mar 07 @ 08:57
Comment from: Dan G. [Visitor] Email · http://grahn.us
This is the code I came up with, as you can see it is MUCH shorter

http://24thwebdesign.freehostia.com/current/current.js
25 Jul 07 @ 23:13
Comment from: mark [Visitor] Email
Great script! This could be just what I have been looking for, however I'm new to all things about web design and I would like to know how this script can be used via a dreamweaver template on a site with 100s of pages with large numbers of pages requiring the same highlighted link in a navigation menu. I need to be able to lock the nav menu into a non-editable region of the template. Can someone please help me?
24 Sep 07 @ 16:50
Comment from: Armand Niculescu [Member] Email · http://www.media-division.com
Mark, there's nothing special about using the script within a DW template -- actually I wrote it specifically to be used with it. For example http://www.whizz.com uses it with Dreamweaver templates.

Have a look at the page with View Source to see how it works (I think I've also updated the javascript code since I wrote the article).
24 Sep 07 @ 18:57
Comment from: Alice [Visitor] Email · http://www.alice.bo.it/
Hello, thanks for the code, is very neat.
I'm trying to include the java-script and instead to call the setPage() in the page, I have this added to the js:

window.onload=function(){
setPage();
}


Thanks again! (love the captcha)
11 Oct 07 @ 01:31
Comment from: Alice [Visitor] Email · http://www.alice.bo.it/
Also, to make it to work you need to replace
setActiveMenu(document.getElementById('navbar').getElementsByTagName('a').extractPageName(hrefString));

with:
setActiveMenu(document.getElementById('navbar').getElementsByTagName('a'),extractPageName(hrefString));
11 Oct 07 @ 01:38
Comment from: Jeremy [Visitor] Email
Thanks for the script! Works great. However, I have a suggestion, related to what Ahmed G said about query strings. It should also be noted that "anchors" at the end of urls also break the script if coming from another page, or simply reloading the page w/ the anchor still attached to the url (i.e. 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:

New code in bold
Altered code in italics


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();
}


Please note, "if statements" to check for "?" and "#" are unnecessary in my code, because if they do not exist, the result of the split is simply a single item array. Much easier than doing the checks and there are no errors either. Hope that's helpful for someone.

[Armand: Excellent post, Jeremy. Thanks!]
17 Nov 07 @ 02:07
Comment from: Tawan [Visitor] Email · http://igarten.com
very good script
Thanks you guys so much
12 Dec 07 @ 18:50
Comment from: Meme11 [Visitor] Email · http://www.topgames.ro
Administrator, you're the man!! This works like a charm! And a million "thank you' to Ahmed G, who's solution for query string pages work great.
So if you have dynamicaly generated pages like this for example: - browse.php?c=1 - you have to modify the "extractPageName" function in highlight.js with Ahmed G's mod:
[code]
function extractPageName(hrefString)
{
var arr = hrefString.split('.');
// Check to see if the url has a query string by searching for the question mark
var hasquestion=hrefString.indexOf('?');
if (hasquestion == '-1')
{
arr = arr[arr.length-2].split('/');
return arr[arr.length-1].toLowerCase();
}
else
{
return arr[arr.length-1].toLowerCase();
}
}
[/code]
for this to work only in current page, otherwise all pages like
browse.php?c=# (where # is the number of the page) will be highlighted.
Hope this helps.
25 Dec 07 @ 11:44
Comment from: Lists [Visitor] · http://www.listomi.com
Great tips.
It's always a pain marking an active page, this looks a great solution.
I'm going to try it right now.

Thanks!
29 Dec 07 @ 01:09
Comment from: Paul Carlisle [Visitor] Email
This is a nice solution, but as Ben Dalzeil pointed out it suffers from a problem: it fails for pages referenced indirectly through their index.html file.

If I invoke the page www.mydomain.org/topic/index.html via www.mydomain.org/topic/, HREF doesn't tack on 'index.html'; it simply passes along the URL with the slash-suffixed pathname. Unfortunately, there doesn't seem to be any way to extract the explicit filename using JavaScript, at least not that I'm aware of.

My not very elegant solution is to maintain a list of objects, each of which stores the title and absolute URL of the page in question. I can then compare the titles, as I can extract document.title with ease - as long as I'm careful to name them the same in both places, which is certainly a shortcoming of this approach.

Using some sort of modified Sitemap that would be read and used to set both the stored titles and the page titles themselves would solve this problem, but suddenly this project is ballooning in size...
27 Jan 08 @ 22:53
Comment from: Tom [Visitor] Email
Was attempting to do this with .NET but it was getting messy. This is a fantastic bit of code :)

Only problem I'm having so far is:

I'm on a page called /about/index.aspx
So where I have "about home" - it highlights it nicely.

However, in my navigation I have a link to /contact/index.aspx

And it highlights that as well. So I have two highlighted items.

Now, it's not a major problem as I just removed the "index.aspx" out of the link to /contact.

But just thought I'd let you all know.
07 Feb 08 @ 15:24
Comment from: gap tooth clan [Visitor] Email
I think this is a great function, would have had to code it myself if not found it.

I have just been investigating jquery looks pretty good, I am sure it could do this more efficiently.

Could do with a load handler and an add class function rather than just changing the class to whatever.

But good work all round
02 Jun 08 @ 17:11
Comment from: Josiah Platt [Visitor] Email · http://www.josiahplatt.com
This is such a brilliant code snippet. I love simple / smart code. Thank you.

: huggle :
23 Jul 08 @ 19:08
Comment from: Rui Pedro [Visitor] Email · http://www.a-fotografia.com
:)
take me some time to make the id=NAV :)

worked gr8
ty
24 Aug 08 @ 21:17
Comment from: Brian [Visitor] Email · http://www.stickstonedesign.com
As Ben Dalzeil and Paul Carlisle pointed out above, this doesn't work on "index" pages that reside in different directories (such as /about/, or /contact/ etc.). Great code otherwise, and I'm glad to see this topic being tackled!

Does anyone have a recommended solution?

25 Aug 08 @ 09:54
Comment from: Strother [Visitor] Email · http://www.fbyc.net
I have the same problem as Tom - 4 posts up. I use Zope and I am forced to have links with a forward slash - like

Club/index_html
Sailing/index_html

and the java script doesn't separate them - and both links are highlighted when you are on one or the other. If I could make the links

Club.index_html
Sailing.index_html

It would work fine. Can you or someone suggest how the javascript should be modified to work with the forward slashes and distinguish between the links. Thanks.
23 Sep 08 @ 00:22
Comment from: Armand Niculescu [Member] Email · http://www.media-division.com
To all having issues with files having the same name in different folders: I actually fixed this an year ago, but I only updated the code on screen, not the one in the archive.

If you download the code again, it should work.
29 Sep 08 @ 10:36

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)
This is a captcha-picture. It is used to prevent mass-access by robots.
Please enter the characters from the image above. (case insensitive)

This is the blog of the Media Division team. We're giving back to the community some of the things we have learned while building all sorts of apps. Concentrating on Flash/Actionscript, we'll also cover C#, MSSQL, ColdFusion/Oracle and areas like Photography and design. We're writing original articles only - no silly stuff to generate more traffic.

Search

XML Feeds

Aggregated by MXNA

Aggregated by MXNA

Related Links