Hello everybody,

I would like to integrate an accordion effect for my sidebar navigation using Jquery.
Therefore I tried to change the output of the function phptemplate_menu_tree.

But unfortunately I am not able to change the php code to get such a listing:

<ul>
    <li class="expanded"><a href="/">menu1</a></li>

    <ul class="menu">
        <li class="leaf"><a href="/">submenu1</a></li>
        <li class="leaf"><a href="/">submenu2</a></li>
    </ul>
</ul>
<ul>
    <li class="expanded"><a href="/">menu1</a></li>

    <ul class="menu">
        <li class="leaf"><a href="/">submenu1</a></li>
        <li class="leaf"><a href="/">submenu2</a></li>
    </ul>
</ul>

My template.php looks like this:


function phptemplate_menu_tree($pid = 1) {
  $msm = variable_get('menu_secondary_menu', 0);
  if ($tree = menu_tree($pid)) {
    $output .= "<ul>"; 
    $ul = $msm == $pid ? '<ul>' : '<ul class="menu">';
    return "\n<ul class=\"menu2\">\n".$tree."\n</ul>\n";
    $output .= "</ul>"; 
    return $output;
  }
}

function phptemplate_menu_item($mid, $children = '', $leaf = TRUE) {
  $msm = variable_get('menu_secondary_menu', 0);
  $menu = menu_get_menu();
  if (in_array($mid, $menu['visible'][$msm]['children'])) {
    return '<li>'. menu_item_link($mid) . $children ."</li>";
  } else {
    return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. menu_item_link($mid)."</li>\n". $children;
  }
}

Has anybody tried to integrate an accordian menu in his site yet and may help?
I am using Drupal 5 with multiflex37 theme.

Thank You!

Comments

Compactman’s picture

Yeah actually I have done it with a module called DHTML Menu. Make sure you download the head of it though because the other versions act a little wonky in multiflex.

stevermeeks’s picture

dhtml menu works like a charm for this without the coding

Vallenwood’s picture

I achieved this effect by rewriting the theme functions just like you were going for. But to finish the job, I had to also rewrite some of the core Drupal menu functions--but NOT by hacking the core. I just copied the core functions menu_tree and menu_item_link into my template.php, renamed them slightly (to menu_tree_full and menu_item_link_accordion), and then in my new theme functions I called these new functions instead of the normal core ones. Now I had a self-contained menu-generating system based on the Drupal one, but not conflicting with it.

In the comments of the phptemplate_menu_tree function I paste below, you'll see the lines:

// Find out the "pid" (ID number) of the menu you want to make an accordion out of. Go to the menu administration page
// and if you click on "Edit" or "Add Item" under the menu you want, it's the last number in the URL. Like /admin/build/menu/menu/edit/2
$accordions = array( 2 );

This requires some manual configuration. When you set up a menu in the Menu admin, and want it to be an accordion, you have to figure out what "pid" it is by following the commented instructions. Then you add it to the array. In my case it was 2. You can add others, comma-separated, and these menus -- and ONLY these menus -- will be rendered by the new accordion-enabled functions. Your other menus will be unchanged.

This was custom written to deal with my site's needs, and my accordion in particular (jQuery Accordion), and assigns CSS classes as such. You may need to modify this to fit your needs, but I hope it'll at least serve as a model for what you can do. I'd paste a link to a finished example, but the site isn't live yet!


/**
 * Returns a rendered menu tree WITH ALL CHILDREN RENDERED. Based on menu_tree() from
 * includes/menu.inc, but meant for accordions, dhtml menus, etc. where all submenus are
 * rendered so they may be displayed or hidden via CSS and JavaScript.
 *
 * @param $pid
 *   The parent id of the menu.
 * @param $accordion
 *   Passed by the first iteration of theme_menu_tree and then onwards recursively.
 * @param $root_accordion_ul
 *   Passed by the first iteration of theme_menu_tree so that the FIRST level of links (the root UL) gets a custom class later when themed.
 */
function menu_tree_full($pid = 1, $accordion = FALSE, $root_accordion_ul = FALSE) {
	$menu = menu_get_menu(); // This grabs the ENTIRE menu structure. Huge. Fortunately, Drupal returns the cached version.
	$output = '';

  if (isset($menu['visible'][$pid]) && $menu['visible'][$pid]['children']) {
		foreach ($menu['visible'][$pid]['children'] as $mid) {
			$type = isset($menu['visible'][$mid]['type']) ? $menu['visible'][$mid]['type'] : NULL;
			$children = isset($menu['visible'][$mid]['children']) ? $menu['visible'][$mid]['children'] : NULL;
			// The following line is the crucial change. This function passes all themed children to the theming function, whereas
			// the Drupal-default function only passes the children IF they are in the active menu trail (i.e. it's expanded) 
			$activepath = menu_in_active_trail($mid);
			$output .= theme('menu_item', $mid, theme('menu_tree', $mid, $accordion), count($children) == 0, $accordion, $activepath, $root_accordion_ul);
		}
  }

  return $output;
}

/**
 * Generate the HTML for a menu tree.
 * Overrides Drupal-default theme_menu_tree
 *
 * @param $pid
 *   The parent id of the menu.
 * @param $accordion
 *   Whether this menu tree is to be output normally ($accordion == FALSE) or with all
 *   children expanded even if not in active menu trail--thus allowing for CSS and Javascript-based
 *   showing and hiding of submenus. Passed by the first iteration, and then onwards recursively.
 */
function phptemplate_menu_tree($pid = 1, $accordion = FALSE) {
	// Find out the "pid" (ID number) of the menu you want to make an accordion out of. Go to the menu administration page
	// and if you click on "Edit" or "Add Item" under the menu you want, it's the last number in the URL. Like /admin/build/menu/menu/edit/2
	$accordions = array( 2 );
	$root_accordion_ul = (in_array($pid,$accordions));
	$accordion = ($root_accordion_ul || $accordion);
	if ($accordion && ($tree = menu_tree_full($pid, $accordion, $root_accordion_ul))) { // Special class for the Primary Links menu, which is always $pid 2 in a normal installation:
		$cssclass = $root_accordion_ul ? ' class="accordionmenu"' : '';
		return "\n<ul$cssclass>\n". $tree ."\n</ul>\n";
	} else if ($tree = menu_tree($pid)) {
		return "\n<ul class=\"menu\">\n". $tree ."\n</ul>\n";
  }
}

/**
 * Generate the HTML output for a single menu item.
 * Overrides Drupal-default theme_menu_item
 */
function phptemplate_menu_item($mid, $children = '', $leaf = TRUE, $accordion = FALSE, $activepath = FALSE, $root_accordion_ul = FALSE) {
	if ($accordion) {
		return '<li class="'. ($leaf ? 'accordionleaf' : (($activepath) ? 'accordionhead defaultaccordion' : 'accordionhead')) .'">'. menu_item_link_accordion($mid, TRUE, $root_accordion_ul) . $children ."</li>\n";
	} else {
		return '<li class="'. ($leaf ? 'leaf' : ($children ? 'expanded' : 'collapsed')) .'">'. menu_item_link($mid) . $children ."</li>\n";
	}
}

/**
 * Returns the rendered link to a menu item. This "accordion" version
 * passes TRUE to the 'menu_item_link' theme function to add a class.
 *
 * @param $mid
 *   The menu item id to render.
 * @param $theme
 *   Whether to return a themed link or the link as an array
 * @param $root_accordion_ul
 *   Whether this item is the root UL of the menu, in which case it is treated slightly differently by the theme function
 */
function menu_item_link_accordion($mid, $theme = TRUE, $root_accordion_ul) {
	$item = menu_get_item($mid);
	$link_item = $item;
	$link = '';
	
	while ($link_item['type'] & MENU_LINKS_TO_PARENT) {
		$link_item = menu_get_item($link_item['pid']);
	}
	
	if ($theme) {
		$link = theme('menu_item_link', $item, $link_item, $root_accordion_ul);
	}
	else {
		$link = array(
			'title' => $item['title'],
			'href' => $link_item['path'],
			'attributes' => !empty($item['description']) ? array('title' => $item['description']) : array()
		);
	}
	
	return $link;
}

/**
 * Generate the HTML representing a given menu item ID.
 * Overrides Drupal-default theme_menu_item_link
 */
function phptemplate_menu_item_link($item, $link_item, $accordionheader = FALSE) {
	// If the item has children, it's an accordion header. Pass a CSS class:
	$cssclass = $accordionheader ? array('class' => 'accordionheader') : array();
	return l($item['title'], $link_item['path'], !empty($item['description']) ? array_merge(array('title' => $item['description']),$cssclass) : $cssclass, isset($item['query']) ? $item['query'] : NULL);
}


nicolefou’s picture

Vallenwood thanks for your great contribution. It's nearly doing what I am looking for, it's just not picking the good menu in my hierarchy. I have searched as you mentioned the pid of the menu and replaced it in the code. But it's picking instead the menu pid of the complete admin architecture.

Example: if I am putting 2, it's displaying "Recent Posts" which is the second item in my admin menu.

Any idea how to fix this?
Thanks
Nicolas

Vallenwood’s picture

Well, you have to manually find and enter the number of the menu you want. The way you locate the "pid" number of the menu you want is to go into the Menu administration screen, then find the menu you want to use. Keep in mind that it must be a ROOT menu, like "Primary Links" or "Navigation" -- I'm pretty sure this won't work right, if at all, if you try to apply it to a "branch" of a higher-level menu. This must apply to a WHOLE menu, or nothing at all.

So let's say you want to apply this effect to the "Primary Links" menu:

  1. When you go to admin/build/menu, you'll see "Primary Links" in big lettering.
  2. Right underneath it you'll see links for "Edit," "Delete," and "Add Item."
  3. Now click "Edit."
  4. You will now be on the Edit Menu page. Look at the URL up in the browser's address bar. It will look something like "www.yoursite.com/admin/build/menu/menu/edit/5" -- except the last number may be any number. Make a note of what that number is. Let's say the number is "5" for this example.
  5. Go into the code, find the function phptemplate_menu_tree and find the line:
    $accordions = array( 2 );
    

    Replace the number "2" with the number "5," since 5 is the number of YOUR desired menu. Your code will now be:

    $accordions = array( 5 );
    

    (If you have another menu you also want to do this with, just add it to the array, so if you want to do this with two different menus, you might have something like array( 5, 16 ))

Now, wherever that menu is rendered, it will render using the new theme functions I pasted here, and output all the child menus.

bloke_zero’s picture

I'm really interested in getting the accordion for a primary menu working in 6.3 . Anyone got any tips?

h_dries’s picture

Is it possible to output in div containers
something like:

<div id="outside"> parent1 <div/>
<div class="insidewrap">
 <div class="inside"> child1.1 <div/>
 <div class="inside"> child1.2 <div/>
 <div class="inside"> child1.3 <div/>
 <div class="inside"> child1.4 <div/>
</div>


<div class="outside"> parent2 <div/>
<div class="insidewrap">
 <div class="inside"> child2.1 <div/>
 <div class="inside"> child2.2 <div/>
 <div class="inside"> child2.3 <div/>
 <div class="inside"> child2.4 <div/>
</div>
//and so on...


and perhaps also with a wrap around every menu parent with childeren


<div id="parentwrap1">

<div class="outside"> parent1 <div/>
<div class="insidewrap">
 <div class="inside"> child1.1 <div/>
 <div class="inside"> child1.2 <div/>
 <div class="inside"> child1.3 <div/>
 <div class="inside"> child1.4 <div/>
</div>

</div>

<div id="parentwrap2">

<div clss="outside"> parent2 <div/>
<div class="insidewrap">
 <div class="inside"> child2.1 <div/>
 <div class="inside"> child2.2 <div/>
 <div class="inside"> child2.3 <div/>
 <div class="inside"> child2.4 <div/>
</div>

</div>

//and so on...

Hueij’s picture

I don't know **** about jscript so I won't comment on that but this looks wrong to me:

<ul>
    <li class="expanded"><a href="/">menu1</a></li>

    <ul class="menu">
        <li class="leaf"><a href="/">submenu1</a></li>
        <li class="leaf"><a href="/">submenu2</a></li>
    </ul>
</ul>

You can't have a an (un)ordered list directly in a parent (un)ordered list.

    <ul>
       <ul>
           ...
      </ul>
    </ul>

is wrong.

Make that:

    <ul>
       <li>
          <ul>
           ...
          </ul>
       </li>
    </ul>
dreipunktnull’s picture

Hi,

I also needed to wrap the second level entries of a menu into a div container for styling purposes. Here's what I did:


function phptemplate_menu_tree($pid = 1) {
    
    $rootMenuIds = array_keys(menu_get_root_menus());
    
    $html = '';
    
    if ($tree = menu_tree($pid)) {
        
        if (!in_array($pid, $rootMenuIds)) {
            $html.= "\n<div class=\"menu\">";
        }
        
        $html.= "\n<ul class=\"menu\">\n" . $tree . "\n</ul>\n";
        
        if (!in_array($pid, $rootMenuIds)) {
            $html.= "</div>\n";
        }
        
        return $html;
        
    }
}

This will result in something like this:

<ul class="menu">
    <li class="leaf"><a href="/node/1" title="Node 1">Node 1</a></li>
    <li class="expanded"><a href="/node/2" title="Node 2" class="active">Node 2</a>
        <div class="menu">
            <ul class="menu">
                <li class="leaf"><a href="/node/3" title="Node 3">Node 3</a></li>
                <li class="leaf"><a href="/node/4" title="Node 4">Node 4</a></li>
            </ul>
        </div>
    </li>
</ul>

Best regards,
Björn