Adam Liptrot

Wrangling code & pushing pixels since 1998.

Two-tier navigation with some JQuery

I came across an interesting job at work recently with a new navigation menu. The project's main navigation bar had changed from a single horizontal bar to one requiring a sub-menu. Before we get started, have a quick look at the finished menu.

No drop-downs

The first decision I made was that I didn't want to use vertical drop-downs for the sub-menus. When a tab was selected I wanted the sub-menu to be fixed in place, allowing quick movement between sections. So these sub-menus would be horizontal, sitting under the main bar. Of course being a good semantic junkie the menu would be marked up as nested lists:


        <div class="simplenav fix">
		<ul class="fix">
			<li><a href="/" title="Return to the the frontpage">Home<br /><span>hello</span></a></li>
			<li><a href="">Projects <span>client work</span></a>
				<ul>
					<li><a href="">By area</a></li>
					<li><a href="">Case studies</a></li>
					<li><a href="">Can we help you?</a></li>
				</ul>
			</li>
			<li><a href="">Products <span>buy me</span></a>
				<ul>
					<li><a href="">Desktop apps</a></li>
					<li><a href="">Web apps</a></li>
					<li><a href="">Mobile apps</a></li>
				</ul>
			</li>
			<li><a href="">Support <span>faq &amp; forums</span></a>
				<ul>
					<li><a href="">FAQ</a></li>
					<li><a href="">Knowledge base</a></li>
					<li><a href="">Tutorials</a></li>
					<li><a href="">Forum</a></li>
				</ul>
			</li>
			<li><a href="">Blog <span>(almost) daily</span></a>
				<ul>
					<li><a href="">Archives</a></li>
					<li><a href="">Blog roll</a></li>
				</ul>
			</li>
			<li><a href="">About <span>our team</span></a></li>
			<li><a href="">Contact <span>get in touch</span></a></li>
			<li class="skip"><a href="/logout/" title="Log Out">Log Out <span>Bye</span></a></li>
		</ul>
	</div>

The basic stuff

With a fixed sub-menu you need to have a way of showing the sub-menu for the given tab. As I was already using server-side code to assign a css class to the active tab, it was an easy addition to hide the unwanted sub-menus with a css rule:


/*show or hide the submenu*/
.on {position: absolute; bottom: 0; left: 0; width: 100%;}
.off {top: -9999999px; position: absolute; opacity: 0;}	

/*highlight the active tab*/
.here>a {background: url(sub.jpg) top left repeat-x;}


<li class="here"><a href="">Support <span>faq &amp; forums</span></a>
	<ul class="off">
		<li><a href="">FAQ</a></li>
		<li><a href="">Knowledge base</a></li>
		<li><a href="">Tutorials</a></li>
		<li><a href="">Forum</a></li>
	</ul>
</li>
<li><a href="">Blog <span>(almost) daily</span></a>
	<ul class="on">
		<li><a href="">Archives</a></li>
		<li><a href="">Blog roll</a></li>
	</ul>
</li>

So far so good.

Hover goodness

Ok, so this is what we want from our hover interaction :

  1. a tab to have a 'selected' state when you are on a page within that section (done with server-side class assignment)
  2. all tabs (except the 'selected' one) to have a 'hover' state (done with basic css)
  3. the 'selected' tab's submenu should be visible by default (the resting state is done as part of 1. but we need to do a bit of work to 'reset' it)
  4. as you hover over the other tabs, their submenus should replace that of the 'selected' submenu
  5. as you move into a submenu, its parent tab should retain its appropriate colour ('selected' or 'hover')

We'll accomplish items 3 - 5 with some jquery.


$(document).ready(function(){
	$(".simplenav>ul>li>a").bind("mouseenter",function(){
		hideAllNav(); //as the cursor moves onto a tab, hide all the submenus ...
		showChildNav(this); // ... before showing the submenu of the hover tab
	});
	
	$(".simplenav").bind("mouseleave",function(){
		//this function is required as the mouse can exit off the bottom of a submenu or a tab, otherwise it'd just 'stick' until you moused over another tab.
		hideAllNav(); //as the cursor moves out of the menu area hide all submenus ...
		showCurrentNav(); // ... before showing the currently active tab (the default setting)
	});
});

function hideAllNav(menu){
	$(".simplenav ul ul").removeClass("on fix"); // take the 'on' class off
	$(".simplenav ul ul").addClass("off"); // apply 'off' class to all submenus
}

function showChildNav(actOnMe){
	//this function ensures the associated tab for the submenu stays 'lit' when you leave the tab and move into the submenu	
	$(".simplenav li").removeClass("MenuVisible"); //remove any existing highlight for the tabs
	$(actOnMe).parent("li").find("ul").removeClass("off");
	$(actOnMe).parent("li").find("ul").addClass("on fix");
	//here we just ensure we aren't assigning the 'hover' colour to the page tab
	$(actOnMe).parent("li").not($("li.here")).find("ul").bind("mouseenter",function(){
		$(this).parent("li").addClass("MenuVisible");		
	}).bind("mouseleave",function(){
		$(this).parent("li").removeClass("MenuVisible"); //as the mouse leaves the submenu, put everything back
	});
}

function showCurrentNav(){
	//only do this if it is currently hidden
	if($(".simplenav li.here ul").hasClass("off")){
		$(".simplenav li.here ul").removeClass("off");
		$(".simplenav li.here ul").addClass("on fix");
	}
}

So, here's what we have so far. Now you'll see we have a bit of an issue, especially with the Support and Blog submenus. The position of the tabs to the submenu items means it can be tough to make a selection without catching one of the neighbouring tabs.

The left-aligned submenu makes it tough to access from a central tab

This isn't ideal and will be frustrating for users, what we want is the submenu to be centred under the relative tab. Now for most websites this would just be a matter of setting a left margin to the left-most submenu item and tada you're done. However that assumes the submenu won't change. Also for my particular application (an extranet), the submenu and top menu both change depending upon what your permissions are on the site, so I can never fix this by hard-coding. Luckily we have already opened our jquery toolbox and it is a pretty easy fix. This is what we're after:

The submenu is centred below its parent tab

What we want to do is measure the distance from the left of the menu to the middle of the current tab (hover or selected), then measure the width of the related submenu divided by two, then take the latter value from the former and assign that as a left-margin. Phew! Here's a diagram which will explain it better:

JQuery has the useful outerWidth method which gives you a numeric value of the full width of an element (as opposed to the width method which doesn't include padding).

With this and a bit of traversing, you can get the measurements you need:


function initialiseNav(navitem){
	//centre of this button
	var widthone = 0;
	widthone = $(navitem).outerWidth();
	widthone = widthone/2;
	
	$(navitem).prevUntil('ul').each(function() {
		widthone = widthone + ($(this).outerWidth());
	});
	
	//width of subnav
	var widthtwo = 0;
	$(navitem).find("li").each(function() {
		widthtwo = widthtwo + ($(this).outerWidth());
	});
	widthtwo = widthtwo/2;
	
	//calculate margin
	var marginvalue = 0;
	marginvalue = widthone - widthtwo;
	
	if(marginvalue>0){
		//set left margin of first subnav item only if it isn't negative
		$(navitem).children("ul").find("li").first().css("margin-left", marginvalue);
	}
}

This gives us our finished menu.

Posted: Tue 2 Mar 2010

Comments

  1. Brave said:

    Hello There,
    Can this be added to a wordpress theme? If so how?
    Please let me know.

    Thank You

    07:15 PM on 9 Mar 2010

  2. N V Varma said:

    Dear Adam,

    That was outstanding.

    I am looking for a similar solution for my project without success.

    I would like to use it with your permission. If you allow me to do so, please send me the source code for the above job.

    Thank you
    And with Regards,
    Varma

    06:01 AM on 11 Mar 2010

  3. Adam said:

    Varma - cheers, feel free to view source on the examples and use it on your project, but please create your own background images.

    08:23 AM on 11 Mar 2010

  4. Loughlin said:

    One of the best tutorials I’ve come across this year.  Fantastic work.

    10:09 PM on 13 Jun 2010

  5. Leslie Ardinger said:

    Dear Adam, you have not only helped me to understand more about CSS and using jquery but you have also enabled me to have greater control over my menu design than I was able to attain with some plugins.  YEAH!!! Thank you!!  Have you ever tried adding a third tier submenu as a dropdown?  I plan on working on that today, but if you could steer me in the right direction it would be very appreciated!  I am a programmer, mostly VB stuff for microsoft products, but am having fun learning jquery and css.  Thanks again!

    01:33 PM on 28 Sep 2010

  6. Adam said:

    Hi Leslie, thanks for the comments.
    I think if you need to add a third layer then you really need to look at what menu system you are using. For that I’d probably use an in-page menu (like a sidebar) for the third layer, otherwise the menu gets too unwieldy and difficult to use for those without excellent mouse skills.

    04:23 PM on 28 Sep 2010

  7. Leslie Ardinger said:

    Thanks Adam...I definitely see what you are saying. If I might ask one other questions that others might have...I am trying to truly tighten up the overall height of the menu system. I have had success altering the CSS for all aspects except the 2nd tier. I tried adding ‘.simplenav ul ul a’ and specifying a height, but this didn’t work. I am trying to wrap my head around all your CSS, but haven’t had any luck yet.  I noticed you have a link to “/assets/reset.css”.  Could there be a setting in this CSS file that is causing me trouble?  Thanks for your thoughts and sharing of this menu system.

    05:29 PM on 28 Sep 2010

  8. Leslie Ardinger said:

    Hi Adam, proud to say I figured it out by making changes to the .on class.  Thanks anyways!

    06:20 PM on 28 Sep 2010

  9. Adam said:

    Leslie, you need to modify two things to change the height of the submenu (you shouldn’t need to add a new rule) - the height of the wrapping ‘simplenav’ div (which pulls in the overall height), and the padding on the ‘a’ tags in the submenu to pull it within the bounds of that div.
    Remember to change the values for IE as well.

    06:24 PM on 28 Sep 2010

  10. Adam said:

    Ah - parallel posting! Nice one.

    06:25 PM on 28 Sep 2010

  11. Leslie Ardinger said:

    Funny...ok...I did come up with something challenging.  I am using a right border to separate the 2nd tier items and do not want the last item in the list to have a right border.  I can’t figure out HOW to name the class which holds the definition {border-right:none;} I have tried many variations due to the fact that you are ‘adding a class’--.on fix when making the menu visible. Any help with this one?  THANKS! Oh, I added <li class="noBorder">.... to the last item in each sub menu.

    07:17 PM on 28 Sep 2010

  12. Adam said:

    Leslie - just use :last-child

    08:37 PM on 28 Sep 2010

  13. Leslie Ardinger said:

    I 100% understand why :last-child is the proper solution--thank you!  I am uncertain WHERE to add this definition.  I have tried adding it into the css as .simplenav>ul>ul>li:last-child {border:none;} but it didn’t work.  I then looked at your jquery and thought it needs to be added somewhere to the class .on ---- still no luck!  Can you embarass me by being precise? LOL

    01:50 PM on 30 Sep 2010

  14. Adam said:

    Your rule will not work because the submenu (ul) is not a direct child of the first menu (ul) - it is contained within a li tag.
    This should work
    .simplenav ul ul li:last-child {...}

    02:30 PM on 30 Sep 2010

  15. Leslie Ardinger said:

    Hi Adam, your css looks just like the one I tried.  The only difference is that I used ‘>’ instead of spaces.  I have included a link to the menu I am struggling with. Any chance you could take a look at why I can’t get rid of the border-right on the last-child?

    09:57 PM on 30 Sep 2010

  16. Sam said:

    Hi Adam,
    This is a fantastic tutorial - thank you! I wanted to ask how you could get a url to open a particular tab on this menu? I have a lot of traversing on my site and it’d be so much more convenient if my page didn’t open to tab #1 each time.

    09:15 AM on 29 Oct 2010

Add your comment