The Goal

An admin-editable, responsive mega menu using Bootstrap 3.x and Drupal without loading up additional javascript or CSS files.

DR Systems Mega Menu

The Problem

Mega Menus are a popular way of displaying a large amount of menu items in a wide format rather than a nested menu. However, they’re often a pain to put together or have a clunky UI to administer or complicated CSS or javascript requirements.

If you’re working on a Drupal site you’ve probably already tried the TB Mega Menu module. You’ve also probably realized it doesn’t play nice if you’re using the Bootstrap theme (or your own subtheme of it). The module loads up the Bootstrap 2.x CSS and javascript, so it conflicts with the 3.x branch of Bootstrap.

The Solution

You can try to rewrite the module yourself or come up with a custom module to modify the menu output. I prefer a simple and reliable solution using a handful of popular and well supported modules.

The ingredients:

  1. Bootstrap Theme
  2. Menu Block
  3. Menu Attributes
  4. Custom Drupal template files

First off, you’re probably going to need a Bootstrap Subtheme. Check out our tutorial here to get that set up.

The Plan:

  1. Organize Your Menu
  2. Use Menu Blocks to break up your menu
  3. Create an array of menu blocks in your template
  4. Rewrite Drupal’s dropdown menu with jQuery
  5. Style the output

1. Organize Your Menu

Organize your menu structure to your liking. Remember that the top level links toggle their dropdown menu and should not go to an actual page.

This is example, “Solutions” is the top level menu item. The second level menu items (“Radiology”, “Cardiology”, etc) will be our mega menu section titles. The only items that will link to other pages are the third tier.

Since we’re going to be rewriting the output of the menu, the actual hierarchy doesn’t matter, but it helps to keep it organized similar to your expected output. All you’re really doing here is organizing your menu into their own branches.

Main menu   DR Systems

 

After you’re done organizing your menu, edit the top level menu item and give it a unique class. You’ll need this later to target its dropdown menu.

Assign Menu Attribute

2. Use Menu Blocks to break up your menu

The next step is break up our second tier menu items into their own blocks. Make sure Menu Blocks is installed and enabled, then go to the Structure > Blocks > Add Menu Blocks (/admin/structure/block/add-menu-block). Toggle over to “Advanced Options”.

  1. Give it an obvious name for the block
  2. Select the menu your items belong to
  3. Specify the parent level menu item (“Fixed parent item”). This is the most important step here.
  4. Save your menu block.

Repeat for the other menu items you want in the menu block. When you’re done you should have one menu block for every set of links in your mega menu.

Radiology  levels 1    block   DR Systems

3. Create an array of menu blocks

This next two steps require some coding. You can either produce a custom module for this or add it to your template file. Assuming you followed our Drupal Bootstrap Subtheme guide, you should have a page.tpl.php and html.tpl.php in your subtheme. To keep this tutorial simple, we’ll put this code into html.tpl.php.

Now that you’ve created your menu blocks, you need to make them available in your template. To do that, it’s most convenient to create an array of the markup to embed (More on this in step 4).

Create a variable to hold an array of your menu block IDs ($menu_block_ids for example). To get these, go to the Structure > Blocks and click on the “Configure” links. The URL will look like this:

[yoursite.com]/admin/structure/block/manage/menu_block/1/configure

The bolded part is the ID.

$menu_block_ids = array( 1, 2, 3, 4, 5, 6, 7 );

Make sure to replace the numbers above with your own menu block IDs.

Next, loop through the array of IDs and generate the markup to render the blocks using module_invoke() and render(). This gets stored as an array for outputting with jquery next. Note that the block title is removed to prevent it from getting passed into render(). The title gets rendered as plain text along with the html for the menu block, so it’s useless for our purposes.

Note that we’re using trim() to remove any whitespace at the beginning of end of the string, and str_replace() to remove any line breaks. This makes the strings easier to pass into jQuery.

<?php

// call the menu blocks so we can insert them into the mega menu
$menu_block_ids = array(1, 2, 3, 4, 5, 6, 7);
foreach ($menu_block_ids as $block_id) {

$megamenu[$block_id] = module_invoke('menu_block', 'block_view', $block_id);
// remove the title
unset($submenu[$block_id]['subject_array']);

// create js friendly string
$megamenu[$block_id]['js'] = trim(str_replace("\n", '', render($megamenu[$block_id])));

}

?>

4. Rewrite Drupal’s dropdown menu with jQuery

By now you should have an array of HTML strings ready to print as variable $megamenu.

If you view your site right now it should appear as a normal Bootstrap dropdown menu a top level menu item (“Solutions” in our example) and the child menu items as a dropdown under it. We use the jQuery .replaceWith() function to target the “Solutions” dropdown menu with that unique class we assigned earlier.

<!-- Navigation items on the mega menu -->
<script>
  jQuery(function($) {
    $(document).ready(function(){
      $("li.solutions ul.dropdown-menu").replaceWith('<ul class="dropdown-menu mega-menu"><div class="container">'
      +'<div class="row"><div class="col-sm-4">'
        +'<h2 class="rad">Radiology</h2><?php print $megamenu[1]["js"]; ?>'
        +'<h2 class="card">Cardiology</h2><?php print $megamenu[2]["js"]; ?>'
        +'<h2 class="path">Pathology</h2><?php print $megamenu[3]["js"]; ?>'
      +'</div>'
      +'<div class="col-sm-4">'
        +'<h2 class="ent">Cloud Communications</h2><?php print $megamenu[4]["js"]; ?>'
        +'<h2 class="inn">Interoperability and Technology</h2><?php print $megamenu[5]["js"]; ?>'
      +'</div>'
      +'<div class="col-sm-4">'
        +'<h2 class="adv">Our Customers</h2><?php print $megamenu[6]["js"]; ?>'
        +'<h2 class="our-solutions">Our Solutions</h2><?php print $megamenu[7]["js"]; ?>'
      +'</div>'
      +'</ul></div></div></div></ul>');
   });
  });
</script>

This code can pasted directly into the html.tpl.php file or in a .js file.

This code replaces the dropdown menu <ul class="dropdown-menu">...</ul> with a 3 column row. Adjust the “col” classes as needed for your own design. We also give it a class of of “mega-menu” to target that tag for styling with CSS. The <h2> tags are assigned unique classes for theming purposes. This is optional depending on your design.

5. Style the output

Style your new megamenu according to your design needs. Since we’re leveraging Bootstraps .row and .col-sm-X classes, the mega menu is automatically responsive.

Additional Notes

  1. We’re using php code to render specific menu blocks in the template. If you want to rearrange the menu blocks or add new ones, you’ll have to do it the template file.
  2. Menu items may be added and rearranged within the menu block from the menu admin pages.
  3. This feature is ran by code that’s rendered in a theme template file, so changing the template will cause your mega menu to disappear.
  4. The second level menu title hardcoded into the template. Changing second the second level menu titles in Drupal’s menu system will not affect the output.
  5. Since we’re rendering the HTML through Menu Block, you’re also given classes for the active menu items and menu trails should you need that for styling.

Each site will be different so it’s not a one-size-fits-all solution and requires some customization.

This tutorial is for Bootstrap 3.x specifically, but the same procedure can be used with any other framework or your own custom CSS.

I hope this tutorial was helpful and provided the tools you need to meet your mega menu needs.

6 Responses to “Tutorial: Drupal Mega Menu with Bootstrap 3.x+ and jQuery”

  1. Sandy

    Thank you for such a wonderful and step by step how to. I am trying to add mega menu for one of my client's bootstrap site. I am able to add mega menu by following your steps, but I am facing two issues. I need your help to resolve the same.

    1) Notice: Undefined variable: submenu in include() (line 86
    I think this line is causing the problem. -> unset($submenu[$block_id]['subject_array']);

    2) Mega menu is going outside of the page (horizontal scrollbar is coming)
    I think the <div class="container"> class is causing the issue

    I am using drupal Bootstrap 7.x-3.5, bootstrap-3.3.6.zip source and jquery 1.10.

    Please help me to resolve this issue.

  2. Robert Waugaman

    Can you provide some guidelines around how to port this to Drupal 8.x?
    For example: is the Menu Attributes module available for Drupal 8.x?
    What's involved with doing this in Drupal 8.x? Can you do it using a custom twig template?

    • gaslamp

      Hi Robert,

      Thanks for your question. We haven't released any production sites on Drupal 8 yet, so I'm unable to give you any guidance here. As soon as we've come up with a solution we'll be sure to share it on our website.

      -Thai

  3. Matt Terry

    The tg megamenu modules looks like it does all this, ui looks neat too. https://www.drupal.org/project/tb_megamenu

  4. Taote

    Great tutorial. Is there a way to achieve the same without using JQuery, I mean on the server side? Using hook menu link to replace the content of the children?

  5. trinsic

    Im having some trouble with this similar to Sandy and I was wondering if we are missing something. You didnt include (where) in the html.tpl.php file we should be putting this code since the default bootstrap code already exists in the file. As such Im getting an error: Notice: Undefined variable: submenu in clude() and was wondering if I am missing something as well. You didn't comment to her reply and I am wondering if we are doing something wrong and we are suppose to know what to do to fix it. I read though your entire instructions and I cant seem to figure out what the problem is.

Comments are closed.