Creating a flexible MenuManager Mega Dropdown menu

In CMSMS Forums there are offten questions about MenuManager Templates, especially about a horizontal MegaDropwdown.
So this time we will have a look at a simple and flexible MenuManager Template that can be used as in few different ways, like simple horizontal Dropdown, Flyout and MegaMenu.

You probably first want to see what we are going to create.

mega_menu.jpg

Before you start with simple Copy/Paste action, don't. This is not a Copy/Paste tutorial and using a menu like this depends on your own Page structure.
Also keep in mind that i used CSS3 (transition, text-shadow, box-shadow) for this demo, which is not supported by all Browser (yeah that IE something in most cases), so you should extend your menu with jQuery fallbacks for those Browser.

Where do we start first?

Well first is a page structure, think about how you want to create your page Sitemap, does it make any sense using a Horizontal menu and then does it make any sense using Mega Dropdown.
If your answer is yes, then go to "Templates » Menu Manager" and create new Template, named as you like. Copy following template.

{strip}
{if $count > 0}
<ul id="menu" class="clearfix">
{foreach from=$nodelist item=node}
    {if $node->depth > $node->prevdepth}
        {if $node->depth == 2}<div class="submenu clearfix"> {/if} {* if depth 2 add div to wrap elements *}
        {repeat string='<ul class="clearfix">' times=$node->depth-$node->prevdepth} {* then continue with usual unordered list *}
    {elseif $node->depth < $node->prevdepth}
        {repeat string="</li></ul>" times=$node->prevdepth-$node->depth}
        {if $node->depth == 2}</li>{/if}
        {if $node->depth == 1}</div></li>{/if} {* close open div's *}
    {elseif $node->index > 0}</li>{/if}
     {if $node->current == true && $node->depth > 1} {* if item is active and depth greate 1 *}
        <li class='item{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <a class="current" href="{$node->url}"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>
                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}{* is there text in options description? *}
                    {if !empty($node->thumbnail) && ($node->depth != 1)}<span class='thumb'><img src='{$node->thumbnail}' alt='{$node->menutext}' /></span>{/if} {* was a thumbnail selected from options? *}
            </a>    {elseif ($node->current == true) or ($node->parent == true)} {* if item is current and has children *}
        <li class='item current{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <a href="{$node->url}" class="menuitem submenuheader current"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}
                    {if !empty($node->thumbnail) && ($node->depth != 1)}<span class='thumb'><img src='{$node->thumbnail}' alt='{$node->menutext}' /></span>{/if}
            </a>
    {elseif $node->haschildren == true && $node->depth > 1} {* if item has children and depth is greater 1 *}
        <li class='item{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <a href="{$node->url}"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>
                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}
                    {if !empty($node->thumbnail) && ($node->depth != 1)}<span class='thumb'><img src='{$node->thumbnail}' alt='{$node->menutext}' /></span>{/if}
            </a>    {elseif $node->haschildren == true} {* if item has children *}
        <li class='item{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <a href="{$node->url}" class="menuitem submenuheader"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>
                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}
            </a>
    {elseif $node->depth > 1} {* if depth is greater 1 *}
        <li class='item{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <a href="{$node->url}"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>
                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}
                    {if !empty($node->thumbnail) && ($node->depth != 1)}<span class='thumb'><img src='{$node->thumbnail}' alt='{$node->menutext}' /></span>{/if}
            </a>
    {elseif $node->type == 'sectionheader' && $node->haschildren == false} {* no link just sectionheader *}
        <li class='item{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <span  href="{$node->url}" class="menuitem sectionheader"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>
                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}
            </a>
    {elseif $node->type == 'sectionheader' && $node->haschildren == true} {* if sectionheader has children *}
        <li class='item{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <span class="menuitem submenuheader sectionheader"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>
                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}
            </span>
    {else} {* anything else *}
        <li class='item{if ($node->haschildren != '1')} no-sub{/if}{if !empty($node->extra1)} {$node->extra1}{/if}'>
            <a class="menuitem" href="{$node->url}"{if $node->target ne ""} target="{$node->target}"{/if}>
                <span>{$node->menutext}</span>
                    {if !empty($node->titleattribute)}<span class='info'>{$node->titleattribute}</span>{/if}
                    {if !empty($node->thumbnail) && ($node->depth != 1)}<span class='thumb'><img src='{$node->thumbnail}' alt='{$node->menutext}' /></span>{/if}
            </a>
    {/if}{/foreach}{repeat string="</li></ul>" times=$node->depth-1}</li></ul>{/if}
{/strip}

Ok this got a bit lenghty and could be improved, but it does it's job. The structure we get with this menu is following.

<ul id='menu' class='clearfix'>
      <li class='item current no-sub'>
            <a href='#' class='menuitem submenuheader current'> <!-- current item -->
                 <span>Home
                   </span>
                  <span class='info'>Description area
                   </span></a>
      </li>
      <li class='item no-sub'>
            <a class="menuitem" href='#'>
                  <span>About
                   </span>
                  <span class='info'>Description area
                   </span></a>
            <div class='submenu clearfix'> <!-- submenu -->
                   <ul class='clearfix'>
                        <li class='item no-sub'>
                              <a href='#'>
                                    <span>Subpage
                                     </span>
                                    <span class='info'>Description area
                                     </span></a>
                        </li>
                        <li class='item no-sub'>
                              <a href='#'>
                                    <span>Subpage
                                     </span></a>
                        </li>
                        <li class='item no-sub'>
                              <a href='#'>
                                    <span>Subpage
                                     </span></a>
                        </li>
                  </ul>
       </div> <!-- end submenu -->
    </li>
</ul>

As you can see what we have is basically a simple unordered list like in all sample Menu template which are included with CMSMS default sample content, but there is a small difference.
To be able to style our mega dropdowns we need a wrapping div around sublevel menu. Also you might have noticed that i used {$node->titleattribute} and {$node->thumbnail} which is used to show a short description of menu item or a thumbnail image, also as menu should be flexible i used {$node->extra1} to apply different classes as needed.

You can control all of these fields when editing a Page within CMSMS under "Options" tab (see Screenshot).
Note:  you could also use CGSimpleSmarty and CGSmartImage for thumbnails and descriptions, for example pulling content summary from one page in to menu.

Menu options

At this moment we have everything ready and all that we need is some styling and learn our Menu to behave properly. As mentioned earlier above, this is not a copy/paste tutorial. 
You should know CSS to at least some level and as you might know my menu style might not  fit every website. The CSS displayed below is used in my Demo, but you can adapt and change it as you need it (see comments in stylesheet).

a {
	color: #fff;
}
/* used for demo page, wrapping content inside 960px */
#wrapper {
	width: 960px;
	margin: auto;
}
/* you might not need this if you use reset */
ul, li {
	margin: 0;
	padding: 0;
}
/* border of thumbnail */
#menu a img {
	border: 1px solid #aaa;
}
/* style of 1st level ul */
ul#menu {
	background: #7d7e7d; /* fallback for not so smart browser */
	/* gradient as background */
	background: linear-gradient(top, #7d7e7d 0%,#0e0e0e 100%); 
	background: -moz-linear-gradient(top, #7d7e7d 0%, #0e0e0e 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#7d7e7d), color-stop(100%,#0e0e0e));
	background: -webkit-linear-gradient(top, #7d7e7d 0%,#0e0e0e 100%);
	background: -o-linear-gradient(top, #7d7e7d 0%,#0e0e0e 100%);
	background: -ms-linear-gradient(top, #7d7e7d 0%,#0e0e0e 100%);
	display: block;
	margin: auto;
	border-radius: 4px; /* rounded border */
}
/* 1st level list is floating for horizontal menu */
#menu li {
	float: left;
	position: relative;
	list-style: none;
	margin: 0 0 0 5px;
	padding: 0;
	/* transition effect for background */
	transition: background .2s ease-in-out;
	-webkit-transition: background .2s ease-in-out;
	-moz-transition: background .2s ease-in-out;
	-o-transition: background .2s ease-in-out;
}
/* links and sectionheaders style */
#menu li a, 
#menu li span.sectionheader {
	display: block;
	color: #fff;
	line-height: 35px;
	padding: 0 16px;
	text-decoration: none;
}
/* 1st level links style */
#menu li a.menuitem, #menu li span.menuitem.sectionheader {
	text-transform: uppercase;
	text-shadow: 1px 1px 0 #aaa;
	color: #000;
	font-weight: bold;
}
/* hover or current style */
#menu li:hover, 
#menu li.current {
	background: #444;
}
/* description is wrapped in info span */
#menu span.info {
	display: block;
	line-height: 1;
	display: block;
	font-size: 11px;
	padding-bottom: 10px;
	text-transform: none;
	text-shadow: none;
	color: #aaa;
	font-weight: normal;
}
/* 2nd level */
#menu .submenu {
	position: absolute;
	z-index: 100;
	width: auto;
	top: -9999em; /* hide first */
	left: -9999em;
	opacity: 0; /* set opactiy to 0 for transtion effect */
	width: 240px; /* flyout will depend on this, if you change it dont forget about #menu .submenu li.flyout ul */
	/* animate dropdown */
	transition: width .4s ease-in-out;
	-webkit-transition: width .4s ease-in-out;
	-moz-transition: width .4s ease-in-out;
	-o-transition: width .4s ease-in-out;
	border-radius: 0 0 6px 6px;
	transition: opacity .4s ease-in-out;
	-webkit-transition: opacity .4s ease-in-out;
	-moz-transition: opacity .4s ease-in-out;
	-o-transition: opacity .4s ease-in-out;
}
/* position wrapping div on hover */
#menu li:hover .submenu {
	top: 35px;
	opacity: .95;
	left: 0;
}
/* if parent has class right, position it to right */
#menu li:hover.right .submenu {
	left: auto;
	right: 0;
}
/* width of mega dropdown, you can add more classes like this, depending on your site structure */
#menu li.two_col:hover .submenu {
	width: 350px;
}
/* reset transition animations */
#menu li li {
	transition: none;
	-webkit-transition: none;
	-moz-transition: none;
	-o-transition: none;
}
/* style of 2nd level unordered list */
#menu .submenu ul {
	background: #444;
	border-radius: 0 6px 6px 6px;
	padding: 10px 0;
	box-shadow: 1px 2px 3px 0px rgba(0, 0, 0, 0.3);
}
#menu .submenu ul li {
	display: block;
	float: left;
}
/* if 2nd level item has no children or has class flyout */
#menu .submenu ul li.no-sub, #menu .submenu ul li.flyout, #menu .submenu ul li.flyout li {
	float: none;
}
/* position 3rd level flyout */
#menu .submenu li.flyout ul {
	position: absolute;
	width: 240px;
	top: -9999em;
	left: -9999em;
}
#menu .submenu li.flyout:hover ul {
	position: absolute;
	left: 230px;
	top: 0;
}
/* style of second level links */
#menu li ul li a.menuitem,
#menu li ul li span.menuitem.sectionheader,
#menu li ul li a, 
#menu li ul li span.sectionheader {
	display: block;
	padding: 0 10px;
	line-height: 24px;
	text-shadow: none;
	transition: color .4s ease-in-out;
	-webkit-transition: color .4s ease-in-out;
	-moz-transition: color .4s ease-in-out;
	-o-transition: color .4s ease-in-out;
}
/* uppercase for mega dropdown parent items */
#menu li ul li a, #menu li ul li.sectionheader span {
	text-transform: uppercase;
	display: block;
}
/* remove uppercase if it's not mega dropdown */
#menu li ul li.no-sub a, #menu li ul li.flyout a, #menu li ul li.no-sub span.sectionheader, #menu li ul li.flyout span.sectionheader {
	text-transform: none;
}
/* link color of sublevels */
#menu li ul li a:hover, #menu li ul li a.current {
	color: #3C76B5;
}
#menu .submenu li ul {
	position: static;
	display: block;
	box-shadow: none;
	border-radius: 0 0 6px 6px;
}
#menu .submenu li ul li {
	float: none;
}
/* width of mega dropdown columns, should fit inside #menu li.two_col:hover .submenu width  */
#menu .two_col .submenu ul li {
	width: 165px;
}
/* clearfix - clearing floats */
.clearfix:after {
	visibility: hidden;
	display: block;
	font-size: 0;
	content: " ";
	clear: both;
	height: 0;
}
/* html .clearfix {
 zoom: 1;
 }
 /* IE6 */
:first-child+ html .clearfix {
	zoom: 1;
}/* IE7 */

Now all you need to do is add appropriate CSS classes to "Extra Page Attribute 1" when editing these. For example if a second level item contains children and third level should behave as flyout, you would add "flyout" as value there. If second level dropdown should behave as Mega Dropdown then you would add "two_col" there (Note: you can change or add additional classes for this part depending on number of sublevels). To align dropdown to right instead of left (for exmaple when using mega dropdown on last main level item, dropdown going over page wrapper wouldn't look that good) you can add "right" as class.

To understand how i used those classes in my demo, it's best to view the SourceCode or inspect it with Browser plugins like Firebug or WebDeveloper or whatever is your tool of choice.

Comments