TJKDesign: Home Page

ez-css Putting the 'less' in table-less layouts. css-101 logo
Bookmark this article at these sites:

How to use the DOM to differentiate navigation links depending on the document which hosts them.

Lately, I have been working on "plug-and-play" solutions (e.g., popup windows, FAQ page) that free HTML documents of unnecessary attributes. This time, it is Gez Lemon's excellent ECMAScript menu article that gave me the idea to write a script that helps to create very lean navigation menus.

This solution does not need any hooks besides an ID for the menu (or the ID of any parent element, for that matter). This is what it'll do for you:

  • It replaces the current anchor with an EM element or plugs a class in that anchor (both options are available to you).
  • It lets you set exceptions in case you wish to treat certain anchors differently (as it does with the "home" link in the sidebar on this page, for example).
  • It can be used with more than one menu per document.

What it will save you from:

Like many web designers/developers, I've been using CSS for years to style current anchors in navigation menus. In the beginning, I was using a class or an ID to style the element differently.
It looked like this:

HTML:

<ul>
<li><a id="current" href="/">Home</a></li>
<li><a href="/about_us.htm">About Us</a></li>
<li><a href="/contact_us.htm">Contact Us</a></li>
<li><a href="/our_services.htm">Our Services</a></li>
</ul>

CSS:

#current {font-weight:bold;cursor:default}

That was simple and very effective, but when I decided to use SS-I to keep my menus in external files, this technique failed and I had to adopt another method.
Using contextual selectors, markup and CSS looked like this:

HTML (for the home page):

<ul id="home">
<!--#include virtual="/inc/navigation.asp" -->
</ul>

Content of "navigation.asp":

<li><a class="home" href="/">Home</a></li>
<li><a class="about" href="/about_us.htm">About Us</a></li>
<li><a class="contact" href="/contact_us.htm">Contact Us</a></li>
<li><a class="services" href="/our_services.htm">Our Services</a></li>

CSS:

#home .home,
#about .about,
#contact .contact,
#services .services {font-weight:bold;cursor:default}

This technique is widely used and works very well. But I think it has a few caveats:

  • If the navigation grows, it becomes difficult to maintain its markup and related CSS rules.
  • It does not improve the experience of screen-reader users. For such users, a difference in style (e.g., background-color, font-weight, etc) is meaningless and they have to listen to a link that points to a document they are already on.

Regarding the former issue, this is the rule I used to use to style the current link inside the main navigation menu for this site:

#s10 #sub10,#s11 #sub11,#s12 #sub12,#s20 #sub20,#s21 #sub21,#s22 #sub22,#s23 #sub23,#s24 #sub24,#s25 #sub25,#s26 #sub26,#s27 #sub27,#s28 #sub28,#s29 #sub29,#s290 #sub290,#s291 #sub291,#s292 #sub292,#s293 #sub293,#s294 #sub294,#s295 #sub295,#s296 #sub296,#s297 #sub297,#s298 #sub298,#s299 #sub299,#s2991 #sub2991,#s2992 #sub2992,#s2993 #sub2993,#s30 #sub30,#s31 #sub31,#s32 #sub32,#s33 #sub33,#s34 #sub34,#s35 #sub35,#s36 #sub36,#s37 #sub37,#s60 #sub60,#s61 #sub61,#s62 #sub62,#s63 #sub63,#s64 #sub64,#s65 #sub65,#s66 #sub66,#s67 #sub67
{cursor:default;color:#fff;background:url(/img/current.gif) left center no-repeat #333;border-top:1px solid #333}

Now consider all the IDs that were plugged in every single anchor in the menu to make this rule work. Pretty scary...

Using the DOM rather than contextual selectors to "mark" the current text link enabled me to remove all instances of these IDs from the markup and replace the whole CSS rule above with the short one below:

#TJK_nav em {
display:block;
font-style:normal;
cursor:default;
color:#fff;
background:#333 url(/img/current.gif) left center no-repeat;
border-top:1px solid #333;
}

And this is just for the main navigation. In this site, I have up to 65 navigation links in some documents (e.g., my frames/frameset article). That's a lot of IDs and/or CLASSes to plug in the markup which translates into many lines in the stylesheets.

In total, this script allowed me to remove close to a hundred attribute/value pairs from the markup and a dozen lines from my stylesheets.

How to use this script:

First, download TJK_hank.zip (1 KB) and unzip the file into a directory within your site. Then, use a script element to link the JS file (TJK_hank.js) to your pages within the head element of your documents, as shown below:

<script type="text/javascript" src="/js/TJK_hank.js"></script>
</head>

Note: In this case, the file has been saved inside a directory called "JS" within the root folder.

Important! Links inside your menus must use root-relative paths (paths starting with a "/") and "href" values within them must not end with a "/" (as in: href="http//www.mydomain.com/sub_directory/").

The TJK_hank() function accepts four parameters:

  1. An ID. The ID of your menu (or the ID of any parent element).
  2. The name of the page your server calls by default; this can be "index.htm", "default.asp", "index.php", etc.
  3. A class name. If you pass a class name to the function (as third parameter), the script will not replace the anchor with an EM, but instead will add this name to the value of the class attribute in the "current" anchor. If there is none, then the attribute will be written along with this class name.
  4. A class name. If you pass a class name here then the script will ignore (skip) any anchor containing that class name.

Use the onload event to call the function. Examples as follow:

  • <body onload="TJK_hank('myMenu','index.html','','')">
    The script will look for anchors inside #myMenu; the default page is "index.html"; the script will replace the "current" anchor with an EM element; no anchor will be skipped (ignored).
  • <body onload="TJK_hank('sideBar','index.php','kilroy','')">
    The script will look for anchors inside #sideBar; the default page is "index.php"; the script will not replace the "current" anchor with an EM element but instead will apply the class "kilroy" to the "current" anchor; no anchor will be skipped (ignored).
  • <body onload="TJK_hank('footer','default.asp','','special')">
    The script will look for anchors inside #footer; the default page is "default.asp"; the script will replace the "current" anchor with an EM element but any anchor containing the class "special" will be skipped (ignored).

Or, better, use a technique like the one explained here: executing JavaScript on page load.

You can also use this script with more than one menu in a document. Consider the following:

<body onload="TJK_hank('Menu','default.asp','','');TJK_hank('Footer','default.asp','kilroy','');">

In this case, the "current" link inside #Menu will be replaced with an EM element while the class "kilroy" will be plugged in the "current" link inside #Footer.

For the curious:

This is the script:

<!--
// TJK_hank() v1.7 Copyright (c) 2006 TJKDesign - Thierry Koblentz
// For help, visit http://www.tjkdesign.com/articles/navigation_links_and_current_location.asp
function TJK_hank(zMenu,defaultPage,classToApply,leaveItAlone){
if (document.getElementById && document.createElement && document.getElementById(zMenu)){
var strLocation = (top.location.pathname.lastIndexOf("/")+1 == top.location.pathname.length) ? top.location.hostname + top.location.pathname+defaultPage : top.location.hostname + top.location.pathname;
	var a = document.getElementById(zMenu).getElementsByTagName("a");
		for (var x=0,i=a.length;x<i;x++){
			var nothingToDo = (a[x] && a[x].className && a[x].className==leaveItAlone) ? true : false;
			if (!nothingToDo && a[x] && a[x].href && a[x].href.lastIndexOf("#")+1!=a[x].href.length){
				if (a[x].href.indexOf(strLocation,0)>0 || a[x].href == top.location.protocol + "//" + top.location.hostname + top.location.pathname){
					if (classToApply==""){
						var objNode = a[x];
						var strLink = objNode.firstChild.data;
						objNode.parentNode.innerHTML="<em>"+strLink+"</em>";
					}else{
						a[x].className += " "+classToApply;
					}
				}
			}
		}
	}
}
//-->

For the DOM police

If I am using this:

objNode.parentNode.innerHTML="<em>"+strLink+"</em>";

rather than this:

var objLink = document.createElement('em'); // won't work in documents served as application/xhtml+xml
objLink.appendChild(document.createTextNode(strLink));
objNode.parentNode.replaceChild(objLink,objNode);

It is because the latter creates duplicates within the Lists ("phantom" ULs), in NN6 and FF 0.8 (and maybe other UAs).

This script should degrade nicely in JS-challenged UAs.
Please use this contact form to send feedback and report errors.