Skip to main content
Edited to add vital final step.
Source Link
bloke_zero
  • 764
  • 4
  • 12
    /**
    * Implements hook_links__system_main_menu().
    *
    * @param array $vars
    * @return string
    *  Themed HTML for bootstrap 3 ready main menu.
    */
    function MYTHEME_links__system_main_menu($vars) {
    // Get the active trail
    $menu_active_trail = menu_get_active_trail();
    // Initialise our custom trail.
    $active_trail = array();

    // Get current path
    $dest = drupal_get_destination();
    if (is_string($dest['destination'])) {
      $paths = explode('/', $dest['destination']);
      // Loop through and add all active paths
      foreach ($paths as $path) {
        // Read previous element added to active trail (using array values
        // preserves original array).
        $safe = array_values($active_trail);
        $previous = array_pop($safe);
        if ($previous) {
          $active_trail[] = $previous . '/' . $path;
        }
        // Or this is the first one
        else {
          $active_trail[] = $path;
        }
      }
    }

    // UL classes
    $class = implode($vars['attributes']['class'], ' ');
    $html = '<ul class="' . $class . '"';
    // Check if there is an ID set (not if it's a dropdown sub-menu).
    if (isset($vars['attributes']['id'])) {
      $html .= ' id="' . $vars['attributes']['id'] . '"';
    }
    $html .= '>';
    // Iterate links to build menu.
    foreach ($vars['links'] as $key => $link) {

      // Check this is a link not a property.
      if (is_numeric($key)) {
        $sub_menu = '';
        $li_class = array();
        $a_class = array();

        // Check if link is in active trail and add class.
        if (in_array($link['#original_link']['link_path'], $active_trail)) {
          $li_class[] = 'active-trail';
        }
        if ($link['#original_link']['link_path'] == end($active_trail)) {
          $li_class[] = 'active';
        }
        // Check if last element in list and see if LI contains actual link
        $link['#attributes']['class'][] = strtolower(str_replace(array('& ', ' '), array('', '-'), $link['#title']));
        $link_title = $link['#title'];
        // Open subscribe in a new window.
        if ($link_title == 'Subscribe') {
          $link['#localized_options']['attributes']['target'] = '_blank';
        }
        if (isset($link['#localized_options']['attributes'])) {
          $link['#attributes'] = array_merge($link['#localized_options']['attributes'], $link['#attributes']);
        }

        // Check if we have a submenu.
        if (!empty($link['#below'])) {
          // Check if lvl 1, if higher do other stuff
          if ($link['#original_link']['depth'] < 2) {
            $li_class[] = 'dropdown';
            $link_title .= '<b class="caret"></b>';
            $link['#attributes']['class'][] = 'dropdown-toggle';
            $link['#attributes']['data-toggle'] = 'dropdown';
          } else {
            $li_class[] = 'dropdown-submenu';
            $link_title .= '<b class="caret"></b>';
          }
          // Theme submenu
          $sub_menu = theme('links__system_main_menu', array('links' => $link['#below'], 'attributes' => array('class' => array('dropdown-menu'))));
        }
        // Build classes string
        $classes = '';
        if (!empty($li_class)) {
          $classes = ' class="' . implode($li_class, ' ') . '"';
        }
        $html .= '<li' . $classes . '>' . l($link_title, $link['#href'], array('html' => 'true', 'attributes' => $link['#attributes'])) . $sub_menu . '</li>';
      }
    }
    $html .= '</ul>';
    return $html;
    }
    /**
    * Implements hook_links__system_main_menu().
    *
    * @param array $vars
    * @return string
    *  Themed HTML for bootstrap 3 ready main menu.
    */
    function MYTHEME_links__system_main_menu($vars) {
    // Get the active trail
    $menu_active_trail = menu_get_active_trail();
    // Initialise our custom trail.
    $active_trail = array();

    // Get current path
    $dest = drupal_get_destination();
    if (is_string($dest['destination'])) {
      $paths = explode('/', $dest['destination']);
      // Loop through and add all active paths
      foreach ($paths as $path) {
        // Read previous element added to active trail (using array values
        // preserves original array).
        $safe = array_values($active_trail);
        $previous = array_pop($safe);
        if ($previous) {
          $active_trail[] = $previous . '/' . $path;
        }
        // Or this is the first one
        else {
          $active_trail[] = $path;
        }
      }
    }

    // UL classes
    $class = implode($vars['attributes']['class'], ' ');
    $html = '<ul class="' . $class . '"';
    // Check if there is an ID set (not if it's a dropdown sub-menu).
    if (isset($vars['attributes']['id'])) {
      $html .= ' id="' . $vars['attributes']['id'] . '"';
    }
    $html .= '>';
    // Iterate links to build menu.
    foreach ($vars['links'] as $key => $link) {

      // Check this is a link not a property.
      if (is_numeric($key)) {
        $sub_menu = '';
        $li_class = array();
        $a_class = array();

        // Check if link is in active trail and add class.
        if (in_array($link['#original_link']['link_path'], $active_trail)) {
          $li_class[] = 'active-trail';
        }
        if ($link['#original_link']['link_path'] == end($active_trail)) {
          $li_class[] = 'active';
        }
        // Check if last element in list and see if LI contains actual link
        $link['#attributes']['class'][] = strtolower(str_replace(array('& ', ' '), array('', '-'), $link['#title']));
        $link_title = $link['#title'];
        // Open subscribe in a new window.
        if ($link_title == 'Subscribe') {
          $link['#localized_options']['attributes']['target'] = '_blank';
        }
        if (isset($link['#localized_options']['attributes'])) {
          $link['#attributes'] = array_merge($link['#localized_options']['attributes'], $link['#attributes']);
        }

        // Check if we have a submenu.
        if (!empty($link['#below'])) {
          // Check if lvl 1, if higher do other stuff
          if ($link['#original_link']['depth'] < 2) {
            $li_class[] = 'dropdown';
            $link_title .= '<b class="caret"></b>';
            $link['#attributes']['class'][] = 'dropdown-toggle';
            $link['#attributes']['data-toggle'] = 'dropdown';
          } else {
            $li_class[] = 'dropdown-submenu';
            $link_title .= '<b class="caret"></b>';
          }
          // Theme submenu
          $sub_menu = theme('links__system_main_menu', array('links' => $link['#below'], 'attributes' => array('class' => array('dropdown-menu'))));
        }
        // Build classes string
        $classes = '';
        if (!empty($li_class)) {
          $classes = ' class="' . implode($li_class, ' ') . '"';
        }
        $html .= '<li' . $classes . '>' . l($link_title, $link['#href'], array('html' => 'true', 'attributes' => $link['#attributes'])) . $sub_menu . '</li>';
      }
    }
    $html .= '</ul>';
    return $html;
    }

And finally add a new $main_menu_expanded for the page template in hook_preprocess_page

  // Get the entire main menu tree
  $main_menu_tree = menu_tree_all_data('main-menu');

  // Add the rendered output to the $main_menu_expanded variable
  $vars['main_menu_expanded'] = menu_tree_output($main_menu_tree);
    /**
    * Implements hook_links__system_main_menu().
    *
    * @param array $vars
    * @return string
    *  Themed HTML for bootstrap 3 ready main menu.
    */
    function MYTHEME_links__system_main_menu($vars) {
    // Get the active trail
    $menu_active_trail = menu_get_active_trail();
    // Initialise our custom trail.
    $active_trail = array();

    // Get current path
    $dest = drupal_get_destination();
    if (is_string($dest['destination'])) {
      $paths = explode('/', $dest['destination']);
      // Loop through and add all active paths
      foreach ($paths as $path) {
        // Read previous element added to active trail (using array values
        // preserves original array).
        $safe = array_values($active_trail);
        $previous = array_pop($safe);
        if ($previous) {
          $active_trail[] = $previous . '/' . $path;
        }
        // Or this is the first one
        else {
          $active_trail[] = $path;
        }
      }
    }

    // UL classes
    $class = implode($vars['attributes']['class'], ' ');
    $html = '<ul class="' . $class . '"';
    // Check if there is an ID set (not if it's a dropdown sub-menu).
    if (isset($vars['attributes']['id'])) {
      $html .= ' id="' . $vars['attributes']['id'] . '"';
    }
    $html .= '>';
    // Iterate links to build menu.
    foreach ($vars['links'] as $key => $link) {

      // Check this is a link not a property.
      if (is_numeric($key)) {
        $sub_menu = '';
        $li_class = array();
        $a_class = array();

        // Check if link is in active trail and add class.
        if (in_array($link['#original_link']['link_path'], $active_trail)) {
          $li_class[] = 'active-trail';
        }
        if ($link['#original_link']['link_path'] == end($active_trail)) {
          $li_class[] = 'active';
        }
        // Check if last element in list and see if LI contains actual link
        $link['#attributes']['class'][] = strtolower(str_replace(array('& ', ' '), array('', '-'), $link['#title']));
        $link_title = $link['#title'];
        // Open subscribe in a new window.
        if ($link_title == 'Subscribe') {
          $link['#localized_options']['attributes']['target'] = '_blank';
        }
        if (isset($link['#localized_options']['attributes'])) {
          $link['#attributes'] = array_merge($link['#localized_options']['attributes'], $link['#attributes']);
        }

        // Check if we have a submenu.
        if (!empty($link['#below'])) {
          // Check if lvl 1, if higher do other stuff
          if ($link['#original_link']['depth'] < 2) {
            $li_class[] = 'dropdown';
            $link_title .= '<b class="caret"></b>';
            $link['#attributes']['class'][] = 'dropdown-toggle';
            $link['#attributes']['data-toggle'] = 'dropdown';
          } else {
            $li_class[] = 'dropdown-submenu';
            $link_title .= '<b class="caret"></b>';
          }
          // Theme submenu
          $sub_menu = theme('links__system_main_menu', array('links' => $link['#below'], 'attributes' => array('class' => array('dropdown-menu'))));
        }
        // Build classes string
        $classes = '';
        if (!empty($li_class)) {
          $classes = ' class="' . implode($li_class, ' ') . '"';
        }
        $html .= '<li' . $classes . '>' . l($link_title, $link['#href'], array('html' => 'true', 'attributes' => $link['#attributes'])) . $sub_menu . '</li>';
      }
    }
    $html .= '</ul>';
    return $html;
    }
    /**
    * Implements hook_links__system_main_menu().
    *
    * @param array $vars
    * @return string
    *  Themed HTML for bootstrap 3 ready main menu.
    */
    function MYTHEME_links__system_main_menu($vars) {
    // Get the active trail
    $menu_active_trail = menu_get_active_trail();
    // Initialise our custom trail.
    $active_trail = array();

    // Get current path
    $dest = drupal_get_destination();
    if (is_string($dest['destination'])) {
      $paths = explode('/', $dest['destination']);
      // Loop through and add all active paths
      foreach ($paths as $path) {
        // Read previous element added to active trail (using array values
        // preserves original array).
        $safe = array_values($active_trail);
        $previous = array_pop($safe);
        if ($previous) {
          $active_trail[] = $previous . '/' . $path;
        }
        // Or this is the first one
        else {
          $active_trail[] = $path;
        }
      }
    }

    // UL classes
    $class = implode($vars['attributes']['class'], ' ');
    $html = '<ul class="' . $class . '"';
    // Check if there is an ID set (not if it's a dropdown sub-menu).
    if (isset($vars['attributes']['id'])) {
      $html .= ' id="' . $vars['attributes']['id'] . '"';
    }
    $html .= '>';
    // Iterate links to build menu.
    foreach ($vars['links'] as $key => $link) {

      // Check this is a link not a property.
      if (is_numeric($key)) {
        $sub_menu = '';
        $li_class = array();
        $a_class = array();

        // Check if link is in active trail and add class.
        if (in_array($link['#original_link']['link_path'], $active_trail)) {
          $li_class[] = 'active-trail';
        }
        if ($link['#original_link']['link_path'] == end($active_trail)) {
          $li_class[] = 'active';
        }
        // Check if last element in list and see if LI contains actual link
        $link['#attributes']['class'][] = strtolower(str_replace(array('& ', ' '), array('', '-'), $link['#title']));
        $link_title = $link['#title'];
        // Open subscribe in a new window.
        if ($link_title == 'Subscribe') {
          $link['#localized_options']['attributes']['target'] = '_blank';
        }
        if (isset($link['#localized_options']['attributes'])) {
          $link['#attributes'] = array_merge($link['#localized_options']['attributes'], $link['#attributes']);
        }

        // Check if we have a submenu.
        if (!empty($link['#below'])) {
          // Check if lvl 1, if higher do other stuff
          if ($link['#original_link']['depth'] < 2) {
            $li_class[] = 'dropdown';
            $link_title .= '<b class="caret"></b>';
            $link['#attributes']['class'][] = 'dropdown-toggle';
            $link['#attributes']['data-toggle'] = 'dropdown';
          } else {
            $li_class[] = 'dropdown-submenu';
            $link_title .= '<b class="caret"></b>';
          }
          // Theme submenu
          $sub_menu = theme('links__system_main_menu', array('links' => $link['#below'], 'attributes' => array('class' => array('dropdown-menu'))));
        }
        // Build classes string
        $classes = '';
        if (!empty($li_class)) {
          $classes = ' class="' . implode($li_class, ' ') . '"';
        }
        $html .= '<li' . $classes . '>' . l($link_title, $link['#href'], array('html' => 'true', 'attributes' => $link['#attributes'])) . $sub_menu . '</li>';
      }
    }
    $html .= '</ul>';
    return $html;
    }

And finally add a new $main_menu_expanded for the page template in hook_preprocess_page

  // Get the entire main menu tree
  $main_menu_tree = menu_tree_all_data('main-menu');

  // Add the rendered output to the $main_menu_expanded variable
  $vars['main_menu_expanded'] = menu_tree_output($main_menu_tree);
Source Link
bloke_zero
  • 764
  • 4
  • 12

I think the problem your running into is that the menu system by default only returns the submenu on the path for that parent menu item.

When I've built bootstrap based themes (and wanted all the submenus expanded for dropdown menus) I've used hook_links___system_main_menu($vars)

It ends up looking like this (can't help but feel there must be an easier way!):

    /**
    * Implements hook_links__system_main_menu().
    *
    * @param array $vars
    * @return string
    *  Themed HTML for bootstrap 3 ready main menu.
    */
    function MYTHEME_links__system_main_menu($vars) {
    // Get the active trail
    $menu_active_trail = menu_get_active_trail();
    // Initialise our custom trail.
    $active_trail = array();

    // Get current path
    $dest = drupal_get_destination();
    if (is_string($dest['destination'])) {
      $paths = explode('/', $dest['destination']);
      // Loop through and add all active paths
      foreach ($paths as $path) {
        // Read previous element added to active trail (using array values
        // preserves original array).
        $safe = array_values($active_trail);
        $previous = array_pop($safe);
        if ($previous) {
          $active_trail[] = $previous . '/' . $path;
        }
        // Or this is the first one
        else {
          $active_trail[] = $path;
        }
      }
    }

    // UL classes
    $class = implode($vars['attributes']['class'], ' ');
    $html = '<ul class="' . $class . '"';
    // Check if there is an ID set (not if it's a dropdown sub-menu).
    if (isset($vars['attributes']['id'])) {
      $html .= ' id="' . $vars['attributes']['id'] . '"';
    }
    $html .= '>';
    // Iterate links to build menu.
    foreach ($vars['links'] as $key => $link) {

      // Check this is a link not a property.
      if (is_numeric($key)) {
        $sub_menu = '';
        $li_class = array();
        $a_class = array();

        // Check if link is in active trail and add class.
        if (in_array($link['#original_link']['link_path'], $active_trail)) {
          $li_class[] = 'active-trail';
        }
        if ($link['#original_link']['link_path'] == end($active_trail)) {
          $li_class[] = 'active';
        }
        // Check if last element in list and see if LI contains actual link
        $link['#attributes']['class'][] = strtolower(str_replace(array('& ', ' '), array('', '-'), $link['#title']));
        $link_title = $link['#title'];
        // Open subscribe in a new window.
        if ($link_title == 'Subscribe') {
          $link['#localized_options']['attributes']['target'] = '_blank';
        }
        if (isset($link['#localized_options']['attributes'])) {
          $link['#attributes'] = array_merge($link['#localized_options']['attributes'], $link['#attributes']);
        }

        // Check if we have a submenu.
        if (!empty($link['#below'])) {
          // Check if lvl 1, if higher do other stuff
          if ($link['#original_link']['depth'] < 2) {
            $li_class[] = 'dropdown';
            $link_title .= '<b class="caret"></b>';
            $link['#attributes']['class'][] = 'dropdown-toggle';
            $link['#attributes']['data-toggle'] = 'dropdown';
          } else {
            $li_class[] = 'dropdown-submenu';
            $link_title .= '<b class="caret"></b>';
          }
          // Theme submenu
          $sub_menu = theme('links__system_main_menu', array('links' => $link['#below'], 'attributes' => array('class' => array('dropdown-menu'))));
        }
        // Build classes string
        $classes = '';
        if (!empty($li_class)) {
          $classes = ' class="' . implode($li_class, ' ') . '"';
        }
        $html .= '<li' . $classes . '>' . l($link_title, $link['#href'], array('html' => 'true', 'attributes' => $link['#attributes'])) . $sub_menu . '</li>';
      }
    }
    $html .= '</ul>';
    return $html;
    }

A bit overkill for what you want as it adds all the bootstrap specific markup, but it's a solution.