Attempting White House style navigation using jQuery and Superfish

I have been loving the redesign of whitehouse.gov that coincided with Obama’s inauguration since, well, his inauguration. One of the things that impressed me the most is its navigational menu. It is a horizontal, pull-down style menu, but what is most interesting to me is the “mega” nature of the sub-menus.

It so happens that I am in the midst of designing a new website for the organization I work for, Northwest Federations of Community Organizations. As part of the process, one of the first steps I wanted to take was to attempt a navigational menu inspired by whitehouse.gov.

Here is a demo, and below are some notes on how I got there.

According to this site, my beloved menu is created using the Superfish jQuery plugin.

I visited the Superfish site and downloaded the zip archive. From the archive’s contents, I used the following:

  • example.html
  • superfish.css
  • jquery-1.4.x.min.js
  • hoverIntent.js
  • superfish.js
  • arrows-ffffff.png
  • shadow.png

First, I customized example.html a bit.

I ended up overriding superfish’s default options by specifying the following:

<pre>
$(document).ready(function() {
	$('ul.sf-menu').superfish({
		delay:         500,                // the delay in milliseconds that the mouse can remain outside a submenu without it closing
		speed:         200,           // speed of the animation. Equivalent to second parameter of jQuery's .animate() method
		dropShadows:   false,               // completely disable drop shadows by setting this to false
	});
});
</pre>

I changed the text of the navigation menu and sub-menus. I wanted to be able to quickly determine parent/child relationships and menu depth. As an example…

Top Level Navigation: A, B, C, etc…
2nd level navigation: A1, A2, A3, etc…
3rd level navigation: A1 A, A1 B, A1 C, etc…

See example 1.

The bulk of the customization happened in the style sheet, superfish.css.

First, I wanted my sub-menus to appear properly “mega.” That is, I wanted the width of the sub-menus to be generously oversized. To do so, I simply ensured that

.sf-menu ul { width: 20em; }

If I were to keep 3rd and 4th level menus, I would want to make sure that the 20em width was reflected in the appropriate classes.

ul.sf-menu li li:hover ul,
ul.sf-menu li li.sfHover ul { left: 20em; }

ul.sf-menu li li li:hover ul,
ul.sf-menu li li li.sfHover ul { left: 20em; }

See example 2.

The second thing I wanted to achieve was to ensure that 3rd and 4th level sub-menus are not visible. To do so, I commented out the following lines.

/* commenting this out, I don't want to see any ul 3 level items and deeper
ul.sf-menu li li:hover ul,
ul.sf-menu li li.sfHover ul {
 left:            20em; /* match ul width
 top:            0;
}
 */

/* commenting this out, it would apply to uls 4 levels deeper, but this is already inherited above
ul.sf-menu li li:hover li ul,
ul.sf-menu li li.sfHover li ul {
 top:            -999em;
}

ul.sf-menu li li li:hover ul,
ul.sf-menu li li li.sfHover ul {
 left:            20em; /* match ul width
 top:            0;
}
 */

See example 3.

Even though I hid the 3rd and 4th level menus, I was still left with the indicator arrow. To get rid of those unwanted arrows, I specified “background: none” in the following lines:

.sf-menu ul .sf-sub-indicator { background: none; }
.sf-menu ul a > .sf-sub-indicator { background: none; }

See example 4.

Next I wanted links in my sub-menu to appear as “columns.” To do so, I specified an approximate “halfway” width for the sub-menu’s list items.

.sf-menu li li { width: 45%; } /* places list items into "columns" */

After playing around with it, I wasn’t too happy with the way some of the longer list items clogged up the floated flow. The solution I found works wonderfully in Firefox, but alas not so well in Internet Explorer (surprised?). If anyone has a better solution, I would love to hear it.

.sf-menu li li:nth-child(odd) { clear: left; } /* this is important so that long items in 2nd level don't make floats weird looking, doesn't seem to work in IE */

See example 5.

The navigational menu still looks weird. I realized that much of this could be corrected through the use of colors, borders, etc…

See example 6.

I was feeling pretty good about the whole thing. But, while testing it all out, I realized that there was a large useability problem. When hovering over a main navigation item, a sub-menu normally appears below and to the right of it’s parent. What about if the parent was at the right-hand side of the screen?

Hover over “H” in example 7.

The folks behind whitehouse.gov solved this problem. The answer is that when a user hovers over a menu item that shows on the right half of the page, a sub-menu appears as normal, but pointed to the left, rather than to the right – as if each half of the navigational menu is a mirror image of the other half.

Hover on the left-hand side and the sub-menu points right

Hover on the left-hand side and the sub-menu points right

Hover on the right-hand side and the sub-menu points left

Hover on the right-hand side and the sub-menu points left

My implementation of whitehouse.gov’s solution is a little bit of a pain in the butt. If you have a suggestion for a better way, please do not hesitate to comment and share below! My solution was to ensure that top level menu items were assigned a unique id and assigned a specific width. Then that id was used in my CSS as follows:

/* for the right half of navigation; basically takes submenu and pushes it leftward, rather than rightward */
/* sucky thing is that need to specify id's */
#level-e ul,
#level-e:hover ul,
#level-e.sfhover ul,
#level-f ul,
#level-f:hover ul,
#level-f.sfhover ul,
#level-g ul,
#level-g:hover ul,
#level-g.sfhover ul,
#level-h ul,
#level-h:hover ul,
#level-h.sfhover ul {
 left: -12em;
 border: 0;
 border-right: 1px solid #fff;
 border-bottom: 3px solid #000;
 }

and

.sf-menu > li { width: 10em; } /* this is necessary for implementing the right 1/2 of nav menu & it's submenu, otherwise don't know how much negative left positioning to apply */

See example 8.

Finally, I took the overall navigational menu and floated it left.

See the final example.

Was this exactly what you needed? Did this how-to suck? Did I leave something out? Do you have a better solution? Comment and share below!


This entry was posted in web design and development and tagged , , , , , , , , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

12 Comments

  1. Mandel
    Posted May 6, 2010 at 10:16 am | Permalink

    Hey Dennis:

    I’m doing something very similar, trying to add superfish drop-downs to a WP blog, and it just won’t show up. I saw your comment on ericmmartin.com’s article about using JQuery with WP and followed it here hoping you’d written about what you did to get it working, but alas, nothing. Any tips? I’ve followed Eric’s tips as far as I can understand them, but with no success. I also followed your ‘themocracy’ link but didn’t get much out of it. I’d be delighted if you had some tips…

  2. Posted May 6, 2010 at 11:32 am | Permalink

    Hi Mandel:

    Thanks for commenting & asking a question. I sometimes wonder if anyone reads this blog, or if I’m just doing it for my own future reference. I hope the info below is helpful.

    1) The key thing that was pointed out on ericmartin.com’s article is that you “enqueue” your javascript/jquery files.

    Here’s what I have in my functions.php – it’s more than you need, but at least you can see my approach – also, make sure to look at the comments.


    /* to load script from non-default location */
    function my_init() {
    if (!is_admin()) {
    // comment out the next two lines to load the local copy of jQuery
    wp_deregister_script('jquery');
    wp_register_script('jquery', 'http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js', false, '1.4', true);
    // adding true at the end is important, so that it loads in footer & reduces potential conflicts with built-in WP javascript files
    wp_enqueue_script('jquery');

    // load hoverIntent.js -- the array ('jquery') declares dependencies
    wp_enqueue_script('hoverIntent', THEM_TEMPLATEURL.'/js/hoverIntent.js', array('jquery'), 'unknown', true);

    // load superfish.js -- the array ('jquery', 'hoverIntent') declares dependencies
    wp_enqueue_script('superfish', THEM_TEMPLATEURL.'/js/superfish.js', array('jquery', 'hoverIntent'), '1.4.8', true);

    // load nav.js -- the array ('jquery', 'hoverIntent', 'superfish') declares dependencies
    wp_enqueue_script('nav', THEM_TEMPLATEURL.'/js/nav.js', array('jquery', 'hoverIntent', 'superfish'), '1.0', true);

    // load jquery.cycle.all.min.js -- the array ('jquery') declares dependencies
    wp_enqueue_script('cycle', THEM_TEMPLATEURL.'/js/jquery.cycle.all.min.js', array('jquery'), '2.75', true);

    // load featured.js -- the array ('jquery', 'cycle') declares dependencies
    wp_enqueue_script('featured', THEM_TEMPLATEURL.'/js/featured.js', array('jquery', 'cycle'), '1.0', true);

    // load jquery-ui.min.js -- the array ('jquery', 'cycle') declares dependencies
    wp_enqueue_script('jquery-ui', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js', array('jquery'), '1.8.1', true);

    // load hcr.js -- the array ('jquery', 'cycle') declares dependencies
    wp_enqueue_script('hcr', THEM_TEMPLATEURL.'/js/hcr.js', array('jquery', 'jquery-ui'), '1.0', true);

    }
    }
    add_action('init', 'my_init');

    2) The relevant piece from the themocracy.com post is that you define the relative location of your theme files – this allows you to know that you’re “pointing” to the right place. I added this to my functions.php before the enqueue section above.


    /* to define location of theme template and relative urls for scripts */
    define(THEM_TEMPLATEURL, get_bloginfo('template_directory'));

    3. each of my customized javascript files has something like this at the top – here’s my nav.js – used not in the post above, but in a WordPress site I’m currently working on.


    jQuery(function ($) {
    /* You can safely use $ in this code block to reference jQuery */
    });

    $(document).ready(function() {
    $('ul.sf-menu').superfish({
    delay: 500, // the delay in milliseconds that the mouse can remain outside a submenu without it closing
    speed: 200, // speed of the animation. Equivalent to second parameter of jQuery's .animate() method
    autoArrows: true, // if true, arrow mark-up generated automatically = cleaner source code at expense of initialisation performance
    dropShadows: false, // completely disable drop shadows by setting this to false
    });
    });

  3. Posted May 6, 2010 at 11:42 am | Permalink

    Also, the above post is based on a “proof of concept” type exercise I was doing. I wanted to see if I could pull off the navigation I wanted in the simplest way possible – using good, old plain html, css & javascript.

    If so, the next step would be expanding the concept & trying to integrate it with WordPress, which I have since done, but have not posted anything about. Your question, Mandel, is inspiring me to write that post.

  4. Mandel
    Posted May 6, 2010 at 1:34 pm | Permalink

    Thanks for getting back to me with such a comprehensive answer!

    I’ve put the code into my functions.php page in the current theme, and it is generating links to both jquery and to the superfish.js file, but I still don’t get the drop-down. This does work on every regular page on the site, so I know that I have configured it correctly there; I assume this is a WordPress related conflict.

    Two quick questions:

    1. Did you customize superfish.js to add this:
    jQuery(function ($) {
    /* You can safely use $ in this code block to reference jQuery */
    });

    in place of the ;function($){ which starts it off? If so, are you not meant to put all the code between the { brackets } ??

    2. Do you use your custom js file instead of the standard block of script in the page itself? I was putting this in the header, but with no luck (and I did try wrapping it in the code block in my 1st question):

    // initialise superfish
    $(document).ready(function() {
    $('ul.sf-menu').superfish({
    autoArrows: false // disable generation of arrow mark-up
    });
    });

    Sigh. This is one of those time-sucking things…

  5. Posted May 6, 2010 at 9:04 pm | Permalink

    1. Did you customize superfish.js to add this:
    jQuery(function ($) {
    /* You can safely use $ in this code block to reference jQuery */
    });
    in place of the ;function($){ which starts it off? If so, are you not meant to put all the code between the { brackets } ??

    Actually, no – I didn’t alter superfish.js at all.

    2. Do you use your custom js file instead of the standard block of script in the page itself? I was putting this in the header, but with no luck (and I did try wrapping it in the code block in my 1st question):

    // initialise superfish
    $(document).ready(function() {
    $(‘ul.sf-menu’).superfish({
    autoArrows: false // disable generation of arrow mark-up
    });
    });

    Oh, yes I created new javascript files. For the navigation, I titled it nav.js & referenced it in the enqueuing step:

    // load nav.js -- the array ('jquery', 'hoverIntent', 'superfish') declares dependencies
    wp_enqueue_script('nav', THEM_TEMPLATEURL.'/js/nav.js', array('jquery', 'hoverIntent', 'superfish'), '1.0', true);

    Notice the version 1.0!

    My first instinct was to put it in the header.php file, but after it didn’t work, I tried putting it as a separate .js file – and, thankfully, that did end up working.

    I treated nav.js along the same “cascading” principle as CSS. My approach to javascript is very hack-y, I don’t really know it all that well.

    I figured if you declare dependencies in the enqueing process, then maybe a “cascading” type principle, like CSS, would apply. First it would process jQuery. Then get supplemented by hoverIntent and Superfish. Finally, by my nav.js. If there was any conflict, then the newest in that stack would rule. Seems sound, right?

    Well I have no idea if that “cascading” principle is correct or not, but the navigation ended up working.

    Out of curiosity, do you have a URL I could look at where you trying this all out?

  6. Mandel
    Posted May 10, 2010 at 8:12 pm | Permalink

    Thanks for the suggestions. I’ll try them and then get back to you with an URL if they don’t work. I do appreciate your time and thoughts on this!

  7. visitor
    Posted May 26, 2010 at 1:48 pm | Permalink

    This was just the thing at just the time — have been futzing around with a very similar thing and your left/right solution is elegant. (And, I saw your comment about visitors, so thought I’d let you know I was here. ;-) )

  8. Posted May 27, 2010 at 6:55 pm | Permalink

    Thanks for commenting, visitor. I appreciate it. On the right/left thing, there’s probably a better way to achieve it, but thanks either way.

  9. Pete
    Posted June 2, 2010 at 4:57 pm | Permalink

    “When hovering over a main navigation item, a sub-menu normally appears below and to the right of it’s parent. What about if the parent was at the right-hand side of the screen?” I have been looking for a solution for this problem for days, and finally stumbled upon your solution, good work. I think you’re right, though, there’s probably a better / more efficient way to do it. If you do find a better solution, will you post here?

  10. Posted June 2, 2010 at 6:22 pm | Permalink

    Hey Pete – yes, of course, I’ll post a better solution if I find it. And, thanks for the feedback. I’m currently working this into a custom WordPress theme. Luckily, WP assigns unique IDs to everything, including top-level menu items. This makes implementing this somewhat awkward solution easier.

  11. Pete
    Posted June 2, 2010 at 9:40 pm | Permalink

    I use a CMS that also assigns unique IDs to everything but doesn’t publish those ID’s on the frontend so it does make your solution difficult to implement using that CMS.

    Not sure if you’ve already seen this…

    http://users.tpg.com.au/j_birch/plugins/superfish/supposition-test/index.html

    … but it looks like work on it has stalled since 2008.

  12. Posted June 2, 2010 at 10:17 pm | Permalink

    No, I haven’t come across Supposition before. I wish I had played with it from the start. Thanks for sharing!

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>