Working with Menus for Themeing in Drupal

In this post, first we look at how to render menu in the template. Afterwards, we look how to manipulate menu html layout and its attributes so the menu can be themed per your design. At last, we go over on having custom menu that is customized in similar way via hooks

1. Build Menu

We like to have menus in the Html scope(THEME_html_process) instead the page scope(THEME_page_process). This is because menu are shared across the page layouts in general, so it is not specific to page per say, however. Drupal comes with the menu already available in the page scope under ‘main-menu’. So, if we want the menu to be available in the html scope we have to create one:

function THEME_preprocess_html(&$vars){{
...
        // Primary nav build links.
        $vars['primary_nav'] = menu_tree(variable_get('menu_main_links_source', 'main-menu'));
}

This builds the render array of main menu and make it available in the html scope. To render the menu itself in html.tpl.php, we would:

    <?php print render($primary_nav); ?>

This take array of main menu generated in preprocess function and renders into html to display in page

2. Overwrite UL Element

Next we overwrite the default ul element and its classes with our custom via hook_menu_tree() as following:

function THEME_menu_tree(&$variables) {
    return '<ul class="nav nav-justified">' . $variables['tree'] . '</ul>';
}
3. Overwrite LI Element

At last, lets overwrite the menu elements themselves. To do so, we use hook_menu_link() as following:

function THEME_NAME_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']);
        }
        else if ((!empty($element['#original_link']['depth'])) && ($element['#original_link']['depth'] == 1)) {
            // Add our own wrapper.
            unset($element['#below']['#theme_wrappers']);
            $sub_menu = '<ul class="dropdown-menu">' . drupal_render($element['#below']) . '</ul>';
            // Generate as standard dropdown.
//            $element['#title'] .= ' <span class="caret"></span>';
            $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';
            $element['#localized_options']['attributes']['data-toggle'] = 'dropdown';
        }
    }

    $element['#localized_options']['attributes']['class'][] = 'agri-nav-item';
    $element['#localized_options']['attributes']['class'][] = 'nav-space';

    //set the parent active when child is currently selected
    if(in_array("active-trail", $element['#attributes']['class'])){
        $element['#attributes']['class'][] = 'active';
        $element['#attributes']['class'][] = 'open';
    }
    // 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";
}

Here, we overriding everything from boostrap_menu_link() and made few changes. Added extra classes to the link element, removed caret and ensure parent of selected element is highlighted.

What If You Have Another Menu?

Say you have another menu at the bottom with different markup and class attributes. This menu id is ‘bottom-menu’

A)Build Menu

Build custom menu similar to the Main menu as following:

function THEME_preprocess_html(&$vars){
...
        $vars['bottom_nav'] = menu_tree('menu-bottom-menu');
        $vars['bottom_nav']['#theme_wrappers'] = array('menu_tree__bottom');
}

In the first line, the menu is build via menu_tree() function that takes the menu id and builds the array to render. In the second line, we specify hook for overriding UL element. There is one by default(hook_menu_tree__menu_MENU_NAME), so instead of overriding, we may have used the default one(i.e. hook_menu_tree__menu_bottom_menu)

B) Overwrite UL Element

With the override function specified, we can overwrite the UL element:

function THEME-NAME_menu_tree__bottom(&$variables) {
    return '<ul class="nav-agri nav-tabs-agri nav-justified-agri">' . $variables['tree'] . '</ul>';
} 

Here we use the hook specified, however. You may as well used the default hook(i.e. hook_menu_tree__menu_bottom_menu)

C)Overwrite LI Element

By default the override hook for LI element is hook_menu_link__MENU_ID() plus the menu id. In our case, the override function hook is hook_menu_link__menu_bottom_menu. So, we overwrite the links as following:

function THEME-NAME_menu_link__menu_bottom_menu(array $variables) {
    $element = $variables['element'];
    $output = l($element['#title'], $element['#href'], $element['#localized_options']);
    return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . "</li>\n";
}

There is no dropdown sub-menu to worry, so we create a link and wrap into the LI element before returning

A Word or Two on User Menu

Its very likely your main menu is different than the user menu, so how to handle the html layout for user menu while main menu is using the default hooks for structuring menus
1. User Tree Menu
The user menu has a custom hook – THEME_menu_tree__user_menu available to structure menu tree as following:

function THEME_menu_tree__user_menu(&$variables){
    return '<ul class="dropdown-menu">'.$variables['tree'].'</ul>';
}

2.User Menu Links
There is custom menuhook – THEME_menu_link__user_menu available to structure the menu item list

3.Menu Template file
There is also custom template file – block–system–user-menu.tpl available for you to override block html as you wish for user menu as well

Leave a Reply

Your email address will not be published. Required fields are marked *