Smarter Web Solutions

Drupal Bootstrap 3 multilevel submenus with hover


We all love Bootstrap framework for so many reasons but, there are quite a few of us who hate its default menu behaviour mainly because it's optimised for touch, with little consideration for desktop devices. My view is that both should be given equal consideration because we're simply not that far along in the future to ignore desktop devices. Judging by developer responses in Bootstrap issue queue this is not about to change and we will most likely never see its menus behaving like they do in Zurb Foundation framework for example.

There has been a lot of discussion in Drupal 7 Bootstrap theme project issue queue relating to revealing submenu's on hover instead of click event which is the default Bootstrap behaviour. The other thing that bothered a lot of people including myself was "dead" parent menu items that wouldn't navigate to a page or node as is the default Drupal menu behaviour as well as, behaviour most people are used to and still expect it at least on desktop devices. Indeed I was the one to originally start the discussion and help find a workable solution in Bootstrap 7.x-2.x issue queue.

Then Bootstrap 3 was released with even more reasons to love it and an extra reason to hate its default menu behaviour - .dropdown-submenu support was removed. Predictably Drupal's Bootstrap theme project issue queue reactivated on the matter just as it did all over the web for non Drupal themes. We cannot nor should we expect Drupal Bootstrap theme project managers to tackle the issue as they are understandably so trying to stick with default Bootstrap behaviours, and they are volunteers after all. But there is a solution I found that I am happy to share, and it is surprisingly simple to implement.


the following tutorial will show how you can quickly adapt your Drupal 7.x-3.0 Bootstrap sub theme to have: Smartmenus Bootstrap 3 multilevel dropdown navbar with hover

  1. Hover Dropdown menus 
  2. Multilevel Sub menus
  3. Working (clickable) Parent/Top menu links

​Most importantly this solution is optimised for mobile and desktop browsers supporting touch, mouse or both inputs at the same time.
and a few extras

1. Add required files
We enable these by downloading and adding an excellent smartmenus plugin for Bootstrap (all credits for this plugin go to Vasil Dinkov of Vadikom Web Ltd.) either by adding the required files via our subtheme's .info or template.php file. The easiest way to add required files is via our subtheme's .info file like this

;; Stylesheets

stylesheets[all][] = addons/bootstrap/jquery.smartmenus.bootstrap.css

; ;----------------------------------
; ; METHOD 1: Bootstrap Source Files
; ;----------------------------------
; ;;;;;;;;;;;;;;;;;;;;;
; ;; Scripts
; ;;;;;;;;;;;;;;;;;;;;;
scripts[] = 'jquery.smartmenus.js'
scripts[] = 'addons/bootstrap/jquery.smartmenus.bootstrap.js'

2. Override default Bootstrap 7.x-3.0 base theme behaviours 
Next we'll need to edit our menu-link.func.php file so that we can override default Bootstrap behaviour like in example shown bellow. We are only commenting out 3 lines here, and example contains comments next to or above each line we are editing so it's all self explanatory. Remember to follow same folder structure in your subtheme when adding overrides /YourBootstrapSubthemeName/theme/menu/menu-link.func.php
! Important: My understanding is that base theme is undergoing some major refactoring and this post will probably need to be updated once version 3.1 is released. 

 * @file
 * menu-link.func.php

 * Overrides theme_menu_link().
function YourBootstrapSubthemeName_menu_link(array $variables) {
  $element = $variables['element'];
  $sub_menu = '';

  if ($element['#below']) {
    // Prevent dropdown functions from being added to management menu so it
    // does not affect the navbar module.
    if (($element['#original_link']['menu_name'] == 'management') && (module_exists('navbar'))) {
      $sub_menu = drupal_render($element['#below']);
	//Here we need to change from ==1 to >=1 to allow for multilevel submenus
    elseif ((!empty($element['#original_link']['depth'])) && ($element['#original_link']['depth'] >= 1)) {
      // Add our own wrapper.
      $sub_menu = '<ul class="dropdown-menu">' . drupal_render($element['#below']) . '</ul>';
      // Generate as standard dropdown.
      //$element['#title'] .= ' <span class="caret"></span>'; Smartmenus plugin add's caret
      $element['#attributes']['class'][] = 'dropdown';
      $element['#localized_options']['html'] = TRUE;

      // Set dropdown trigger element to # to prevent inadvertant page loading
      // when a submenu link is clicked.
      $element['#localized_options']['attributes']['data-target'] = '#';
      $element['#localized_options']['attributes']['class'][] = 'dropdown-toggle';
	  //comment element bellow if you want your parent menu links to be "clickable"
      //$element['#localized_options']['attributes']['data-toggle'] = 'dropdown';
  // On primary navigation menu, class 'active' is not set on active menu item.
  // @see https://drupal.org/node/1896674
  if (($element['#href'] == $_GET['q'] || ($element['#href'] == '<front>' && drupal_is_front_page())) && (empty($element['#localized_options']['language']))) {
    $element['#attributes']['class'][] = 'active';
  $output = l($element['#title'], $element['#href'], $element['#localized_options']);
  return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";

3. Optional: Enable default smartmenus plugin behaviour for mobile devices.
With changes we made so far we have all 3 behaviours working on desktop devices. However, on mobile devices clicking on parent menu still opens the submenu dropdown and closes it on second click which is default Bootstrap behaviour and makes it impossible to navigate to parent menu page. Menu's behaving differently on different device types is a really bad UX so I opted to use the default smartmenus behaviour for mobile devices: one click on parent menu item opens dropdown submenu, on second click it will navigate to parent menu page, and in my view this provides a more natural user experience. 
Once again, to enable this behaviour we need to make minimal changes to /addons/bootstrap/jquery.smartmenus.bootstrap.js by commenting out a few lines of code around line 50 as follows

				// click the parent item to toggle the sub menus (and reset deeper levels and other branches on click)
				//'click.smapi': function(e, item) {
//					var obj = $(this).data('smartmenus');
//					if (obj.isCollapsible()) {
//						var $item = $(item),
//							$sub = $item.parent().dataSM('sub');
//						if ($sub && $sub.dataSM('shown-before') && $sub.is(':visible')) {
//							obj.itemActivate($item);
//							obj.menuHide($sub);
//							return false;
//						}
//					}
//				}


And that's all it takes. End result however, is amazing. Not only do you get your menus to behave the way most people still expect them to on desktops but, you also have them behaving great on mobile, touch devices as well.

Some of the key features quoted from author's website

  • Optimised for mobile and desktop browsers supporting touch, mouse or both inputs at the same time
  • Section 508 compliant and fully accessible to assistive technology like screen readers
  • Unlimited menu trees on the same page and unlimited sub menu levels supported
  • Horizontal or vertical main menu items arrangement
  • Absolute/relative/fixed positioning for the main menus supported
  • Right-to-left and bottom-to-top display of the sub menus is possible
  • Full support for RTL text/pages (e.g. Hebrew, Arabic)
  • Full window size detection - the menus will always be kept inside the window's boundaries
  • Automatically adjustable width for the sub menus is possible (including min/max settings)
  • Keyboard navigation friendly (Tab key)

If you're still not convinced checkout the demo and decide for yourself. As always, if you have a better solution or a question feel free to post a comment. And don't forget to share smiley

Authored by: -- Tue, 18/11/2014 - 23:28