10

When landing on a page which has a menu item that is a parent to other pages menu items, I would like to be able to show a list of those child menu items. I am using the following code.

$trail = menu_get_active_trail();
menu_build_tree('main-menu', array(
  'expanded' => array($trail[1]['mlid'])
));

However, the returned array looks like this (with a lot of unnecessary removed).

array
'49950 PKY Truck Beauty 312' => 
array
  'link' => &
    array
      'menu_name' => string 'main-menu' (length=9)
      'mlid' => string '312' (length=3)
      'plid' => string '311' (length=3)
  'below' => 
    array
      '49952 Seminars 314' => 
        array
          'link' => &
            array
              'menu_name' => string 'main-menu' (length=9)
              'mlid' => string '314' (length=3)
              'plid' => string '311' (length=3)
      '50000 HDMA Breakfast 316' => 
        array
          'link' => &
            array
              'menu_name' => string 'main-menu' (length=9)
              'mlid' => string '316' (length=3)
              'plid' => string '311' (length=3)
      '50000 MATS Concert 315' => 
        array
          'link' => &
            array
              'menu_name' => string 'main-menu' (length=9)
              'mlid' => string '315' (length=3)
              'plid' => string '311' (length=3)

Notice how 314, 315, and 316 are 'below' 312? They are siblings in my menu structure, and that seems to be verified by each one having the same value for plid (311). Obviously I can fix this by passing the array through another function, but I can't help but think I'm just missing something.

  • In the interest of time, I'm fixing the issue with CSS, although I'm not happy with it. $tree = menu_build_tree('main-menu', array( 'expanded' => array($trail[1]['mlid']) )); drupal_render(menu_tree_output($tree)) Then using CSS I can style the links to remove the ul padding, making them appear they are all on the same level. Not ideal, but effective. EDIT: sorry, I can't figure out how to get line breaks to work. – Chris Rockwell Jul 25 '12 at 14:22
  • couldn't you post a sample screenshot of what you would like to achieve? To be honest, I found the question a little bit messy (I dare to say that because no answers have been written yet). Where would you like to be able to show the child items? Why aren't the menu related modules satisfying? Please clarify the question a little bit more, and maybe we could find a good solution. – Sk8erPeter Aug 1 '12 at 13:27
  • @Sk8erPeter, I apologize if it's messy. The solution I went with (referenced in my comment) is being used here: link. The main question is: why is menu_build_tree() returning a nested array that has unexpected levels (all links should be on the same)? To see where I'm showing the child items, use the link I included and click on any link in the main nav bar (css is used to give the illusion they are not clickable). – Chris Rockwell Aug 2 '12 at 14:20
  • Regarding the modules, a cursory glance did not turn up anything that would suffice. That could be because I wasn't very interested in installing another module for a solution that should be accomplished in 4 or 5 lines of code. I already have a custom 'includes' module that I use for things like this. Now, from anywhere, I call get_sub_menu_based_on_active_page() and I'm all set. I had to move on from trying to figure out the nesting issue as css makes the user none the wiser. – Chris Rockwell Aug 2 '12 at 14:20
  • 1
    I posted an answer with another approach, I think that is one of the easiest solutions. And it works correctly. The suggested module is really popular amongst Drupal users. – Sk8erPeter Aug 3 '12 at 14:06
8

Just wanted to follow-up. I came back to this question and found the following: https://stackoverflow.com/questions/2716787/how-to-get-all-the-menu-items-below-a-certain-parent-in-drupal which is exactly what I needed.

The code, copied from the above-mentioned link and modified to fit my needs (mainly to use the current path to build the menu tree from, rather than using a hard-coded value:

$parent = menu_link_get_preferred($_GET['q']);
$parameters = array(
  'active_trail' => array($parent['plid']),
  'only_active_trail' => FALSE,
  'min_depth' => $parent['depth']+1,
  'max_depth' => $parent['depth']+1,
  'conditions' => array('plid' => $parent['mlid']),
);

$children = menu_build_tree($parent['menu_name'], $parameters);

return '<div class="content-sub-menu content-padder">' . drupal_render(menu_tree_output($children)) . '</div>';
  • 1
    This works well, but you should use current_path() instead of $_GET['q']. $_GET['q'] will return the path alias, where current_path() will get node/id. – mediaashley Apr 14 '14 at 15:27
6

It can easily be done with using Menu block module (it takes about 5 minutes to configure it).

Menu block screenshot

All you have to do is

  1. Enabling the module
  2. Going to admin/structure/block
  3. Clicking "Add menu block"
  4. Set "Starting level" to "2nd level (Secondary)" and setting the region where it should be displayed under "Specify in which themes and regions this block is displayed."

  5. Screenshots:

    • This is the way the configuration page looks like

      screenshot

    • admin/structure/block page with the enabled Menu block module blocks

      screenshot

    • I generated some "Basic page" contents with Devel module, and provided some menu links to them, and created a nested menu hierarchy

      • This is the default front page with no submenus (The "Main menu - 2nd level" block can NOT be seen in the left sidebar, as it doesn't have any second level child items)

      screenshot

      • This is the second menu, with some child elements, you can already see the "Main menu - 2nd level" in the left sidebar, but only the 2nd level child elements can be seen

        screenshot

        second level items

      • Now going deeper:

        Third level child elements can also be seen

I think that using Menu block module for this task is one of the easiest and fastest solutions.

  • I would be really curious why I got a downvote for this post. The recommended module does the job, and I wrote a step-by-step tutorial. Why doesn't the downvoter post a comment of the reasons? (Maybe it would be useful (maybe not), I could at least react.) – Sk8erPeter Sep 6 '15 at 15:00
0

As noted in the comments, I ended up using the API function and then styling with CSS:

/* --------------- *\
    Sub-menus
    used on main pages
\* --------------- */
.content-sub-menu ul.menu {
  list-style: none;
  padding: 0;
  margin: 0;
}
.content-sub-menu > ul.menu {
  margin-bottom: 25px;
}
.content-sub-menu ul.menu a {
  font-size: 1.5em;
  padding: 10px;
  margin-top: 5px;
  display: inline-block;
  min-width: 33%;
}
0

This functions working correctly to get sub-menu of current page:

function build_left_menu()
{
    global $language_url;
    static $use_theme = NULL;
// Get the entire main menu tree
    $left_menu_tree = menu_tree_all_data('main-menu'); // Your main menu
$left_menu_tree_values = array_values($left_menu_tree); //get value only
    $html = "<div id=\"sidemenu\"><ul id=\"side-nav\" class=\"side-nav-content\"><h3>In this section:</h3>";
foreach ($left_menu_tree_values as $index => $item) {
        $link = $item["link"];
        if ($index === 0) {
            $item_class = "first-item panel side-menu ";
        } else {
            $item_class = "panel side-menu ";
        }
        $options_anchor = array();
        if ($item["below"]) {
            $options_anchor = array("attributes" => array('class' => array('dropdown-toggle'),
                'data-toggle' => 'dropdown',
                'data-delay' => 1000,
                'data-close-others' => "false",
                'data-hover' => "dropdown",
            )
            );
        }
        // Merge in defaults.
        $options_anchor += array(
            'attributes' => array(),
            'html' => FALSE,
        );

        //Default needed class
        $options['attributes']['class'][] = 'subpage-link collapsed';
        // Append active class.
        if (($link['link_path'] == $_GET['q'] || ($link['link_path'] == '<front>' && drupal_is_front_page())) &&
            (empty($options_anchor['language']) || $options_anchor['language']->language == $language_url->language)) {
            if ($item["below"]) {
                foreach ($item["below"] as $item_below) {
                    $link_below = $item_below["link"];
                    $options_anchor = array();
                    $html .= "<li class='" . $item_class . "'>";
                    $html .= override_left_l($link_below['link_title'], $link_below['link_path'], $options_anchor).'</li>';
                }
            }
        }
    }
    $html .= "</ul></div>";
    return $html;
}

Hope this help!

  • Your function calls an undefined function (override_left_l)! – DrCord Sep 23 '14 at 16:51
0

@Chris Rockwell and @ Mario Awad

I am newbie in Drupal so its hard to understand for me where to add this function.. I apologize for that. But, can you guys please mentioned it that In which file, should we add this function?

I am trying to create side bar navigation that will display only child elements on each page. How to create navigation menu on sidebar with different navigation links displayed on different pages?

I Appreciate!

Thanks!

0

I have just finished posting a function that gets child menu items given a node's path. You can check it here: http://softkube.com/blog/getting-child-menu-items-drupal-menu-tree

I'm including the link to future proof the answer in case the post gets updated and I'll also copy/paste the full code at the end.

In your case, you can simply run something like this in your theme to list all child menu items. Modify the echo statement and theme to your liking.

$path = current_path();
$nids = skl_get_all_menu_node_children_ids($path);
$children = node_load_multiple($nids);
foreach($children as $c) {
    echo $c->title . ': ' . url('node/' $c->nid) . '<br />';
}

And here's the function's full code. Check the link for possible future updates.

Good luck.

/**
 * Returns node ids of all the child items, including children of children
 * on all depth levels, of the given node path. Returns an empty array
 * if any error occurs.
 * 
 * @param string $node_path
 * @return array
 */
function skl_get_all_menu_node_children_ids($node_path) {
    //Stop and return an empty array if node path is empty
    if(empty($node_path)) {
        return array();
    }

    //Init empty array to hold the results
    $nids = array();

    //Init parent keys. Check 'foreach' loop on parent keys for more info.
    $parent_keys = array('plid', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9');

    //Collect menu item corresponding to this path to begin updates.
    //Reference: http://stackoverflow.com/a/11615338/136696
    //Note: we couldn't find a way to get the sub-tree starting from this item
    //only and hence we had to get the whole menu tree built and then loop on
    //the current item part only. Not so bad considering that Drupal will
    //most probably have the whole menu cached anyway.
    $parent_menu_item = menu_link_get_preferred($node_path);

    //Stop and return empty array if a proper current menu item couldn't be found
    if(empty($parent_menu_item['menu_name']) || empty($parent_menu_item['mlid'])) {
        return array();
    }

    //Init parent item mlid for easier usage since now we know it's not empty
    $parent_menu_item_mlid = $parent_menu_item['mlid'];

    //Build whole menu based on the preferred menu_name gotten from this item
    $menu = menu_build_tree($parent_menu_item['menu_name']);

    //Reset menu cache since 'menu_build_tree' will cause trouble later on after 
    //you call pathauto to update paths as it can only be called once. 
    //Check: https://www.drupal.org/node/1697570
    menu_reset_static_cache();

    //Init processing array. This will hold menu items as we process them.
    $menu_items_to_process = array();

    //First run to fill up the processing array with the top level items
    foreach($menu as $top_level_menu_item) {
        $menu_items_to_process[] = $top_level_menu_item;
    }

    //While the processing array is not empty, keep looping into lower
    //menu items levels until all are processed.
    while(count($menu_items_to_process) > 0) {
        //Pop the top item from the processing array
        $mi = array_pop($menu_items_to_process);

        //Get its node id and add it to $nids if it's a current item child
        //Note that $parent_keys contains all keys that drupal uses to
        //set a menu item inside a tree up to 9 levels.
        foreach($parent_keys as $parent_key) {
            //First, ensure the current parent key is set and also mlid is set
            if(!empty($mi['link']['mlid']) && !empty($mi['link'][$parent_key])) {
                //If the link we're at is the parent one, don't add it to $nids
                //We need this check cause Drupal sets p1 to p9 in a way you
                //can easily use to generate breadcrumbs which means we will
                //also match the current parent, but here we only want children
                if($mi['link']['mlid'] != $parent_menu_item_mlid) {
                    //Try to match the link to the parent menu item
                    if($mi['link'][$parent_key] == $parent_menu_item_mlid) {
                        //It's a child, add it to $nids and stop foreach loop.
                        //Link_path has the path to the node. Example: node/63.
                        if(!empty($mi['link']['link_path'])) {
                            $nids[] = str_replace('node/', '', 
                                      $mi['link']['link_path']);
                            break;
                        }
                    }
                }
            }
        }

        //Add its child items, if any, to the processing array
        if(!empty($mi['below']) && is_array($mi['below'])) {
            foreach($mi['below'] as $child_menu_item) {
                //Add child item at the beginning of the array so that when
                //we get the list of node ids it's sorted by level with
                //the top level elements first; which is easy to attain
                //and also useful for some applications; why not do it.
                array_unshift($menu_items_to_process, $child_menu_item);
            }
        }
    }

    //Return
    return $nids;
}
  • Thanks Mario. Can you comment on why you chose this over using $parameters in menu_build_tree? – Chris Rockwell Oct 30 '14 at 22:40
  • Thanks for the feedback. I couldn't get the needed info no matter what $parameters I used, and believe me I looked a lot. All I wanted is, given a path, get all child menu items on all levels, and I simply couldn't find a way. If there's one please inform me I'll be glad to learn and update my answer and blog post. Cheers. – Mario Awad Oct 31 '14 at 10:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.