My earlier simple mega menu implementation post displayed some simple css and jquery to explode a standard navigation menu into a mega-menu… I’ve made it even better. My biggest issue with that implementation was that it did not keep the order like you’d expect. It read left to right in columns rather than down each column. In the example you can see the first column of three would read from the top: a, d, g, j… this could potentially be confusing. So I wanted to update it to keep the order better and just stack the columns of elements rather than the elements themselves.

I used some different jquery to execute this. First we walk through the menu elements and calculate which column they should be in. We basically map that element’s (li)index to the column it should be, some big math. Luckily I had some experience from actionscript in my arsenal doing just that, so porting the function to javascript I was ready to go. If your number X falls between A and B, and you would like to convert it to Y which falls between C and D follow this formula: Y = (X-A)/(B-A) * (D-C) + C. Plugging this function in and cancelling out the zeros and adding some rounding to get integers I got: Math.floor((liindex / \$total * \$cols)+1). Using this I added a class to each ‘li’ designating which column it should be in, and then used wrapAll to wrap them into column divs. Very simple and a much better implementation overall anyways. Better code, better user experience… what more can you ask… so here’s the example and jquery code. I’m thinking I should make this into a jquery plugin or something, any thoughts?

Javascript code

[cc lang=”javascript”]
//clean up the row of the mega menu. add css class to each element on bottom row.
//only if more than 7 elements. if more than 16, mm-3
jQuery(‘#nav li ul’).each(function(ulindex, ulele){
\$total = jQuery(this).children(‘li’).size();
if (\$total <= 7) { jQuery(this).addClass('mm-1'); } else { \$cols = Math.floor((\$total) / 8) + 1; \$remainder = \$total % \$cols; \$rows = Math.ceil(\$total / \$cols); jQuery(this).addClass('mm-' + \$cols + ' total-' + \$total + ' rem-'+\$remainder ); jQuery(this).children().each(function(liindex, liele){ //alert("total: "+\$total+", remainder: "+ \$mod+", ulindex: "+ulindex+", liindex: "+liindex); //If your number X falls between A and B, and you would like to convert it to Y which falls between C and D follow this formula: Y = (X-A)/(B-A) * (D-C) + C. jQuery(this).addClass('col-' + Math.floor((liindex / \$total * \$cols)+1) ); if( (liindex+1) % \$rows == 0) { jQuery(this).addClass('last'); } }); for (var colcount = 1; colcount<= \$cols; colcount++){ jQuery(this).children('.col-'+colcount).wrapAll('

‘);
}
}
});
});
[/cc]

css

[cc lang=”css”]
ul { list-style:none; }

/********** < Navigation */ .nav-container { float:left; background: #398301; margin: 10em 0; width: 960px; } #nav { border: 0px none; padding:3px 0 2px 44px; margin:0; font-size:13px; } /* All Levels */ #nav li { text-align:left; position:relative; } #nav li.over { z-index:999; } #nav li.parent {} #nav li a { display:block; text-decoration:none; } #nav li a:hover { text-decoration:none; } #nav li a span { display:block; white-space:nowrap; cursor:pointer; } #nav li ul a span { white-space:normal; } /* 1st Level */ #nav li { float:left; } #nav li a { float:left; padding:5px 10px; font-weight:normal; color: #fff; text-shadow: 1px 1px #1b3f00; } #nav li a:hover { color: #fff; text-shadow: 1px 1px 3px #ccc; } #nav li.over a, #nav li.active a { color:#fff; } /* 2nd Level */ #nav ul { position:absolute; width:15em; top:26px; left:-10000px; border:1px solid #1b3f00; border-width: 0 1px 2px 1px; background:#398301; padding: 6px 0 6px; } #nav ul div.col { float:left; width: 15em; } #nav ul li { float:left; padding: 0; width: 15em; } #nav ul li a { float:none; padding:6px 9px; font-weight:normal; color:#FFF !important; text-shadow: 1px 1px #1b3f00; border-bottom:1px solid #1b3f00; background:#398301; } #nav ul li a:hover { color:#fff !important; text-shadow: 1px 1px 3px #ccc; background: #2b6301; } #nav ul li.last > a { border-bottom:0; }
#nav ul li.last.parent > a { border-bottom:0; }

#nav ul li.over > a { font-weight:normal; color:#fff !important; background: #1b3f00; }
#nav ul.mm-1 { width: 15em; }
#nav ul.mm-2 { width: 30em; }
#nav ul.mm-3 { width: 45em; }
#nav ul.mm-4 { width: 60em; }
/* 3rd+ leven */
#nav ul ul { top:-6px; background: #1b3f00; }

#nav li.over > ul { left:0; }
#nav li.over > ul li.over > ul { left:14em; }
#nav li.over ul ul { left:-10000px; }

/* Show Menu – uses css only, not fully across all browsers but, for the purpose of the demo is fine by me */
#nav li:hover > ul { left:0; z-index: 100; }
#nav li:hover > ul li:hover > ul { left:14em; z-index: 200; }
#nav li:hover ul ul { left:-10000px; }
[/cc]

Visit this demo page and view source or save as…

6 thoughts on “Better jquery mega-menu tutorial”

1. JavaFlactionscript says:

Hey! I love the new design, it looks great. And I’m talking about both the blog and the menu. You’re my hero.

2. First of all, thanks for the code, been looking for 2 days.

The code works well but I cant seem to get Magento to read the Javascript. I’ve placed the jquery.js and script everywhere and magento still cant seem to find it. At least that’s what I assumed as the category just wont move to the next column after added more than 10 of them.

Any idea where’s the right place to place the script?

1. @Ashton – it looks like you’ve solved the issue? I was going to suggest that the css may be conflicting with something in your theme.

3. John Decker says:

First of, you’re brilliant!

How do you dictate when a new column in a submenu begins and ends?

How to make the total width of the submenu wider?

Thank you again.

1. Hey John,

The li’s are split into columns evenly. This js does some math to split them. The widths are all controlled via css (#nav ul li) as well as the container uls in each column.

4. Brief says:

Great can you acheive the same for the toplinks?