I have a render array made by calling menu_tree_output
on my main menu. The menu contains first-level options, naturally enough, and some of these have second-level children. I need to reformat my menu so that when output by render
or drupal_render
, each list of second-level options appears not just as <ul>
and <li>
elements, but as a more complicated chunk of HTML containing the links. (These chunks will implement dropdown menus with icons and subgroup headings inserted between and above links.) Reading http://themery.com/book/export/html/100 , "Using the Render API", I thought render arrays would be ideal. I could transform each list and shove the result back into the render array as raw markup. However, I'm having trouble finding the render arrays that represent the second-level menu options.
Putting my problem in more abstract terms, I've got a menu render array holding a list of lists Li. Each Li is a <ul>
containing <li>
and <a>
elements. In the render array, I want to replace each Li by f( Li ), where f is a PHP function that I'll implement. It will insert HTML between Li's elements and around them.
I started by doing
$tree = menu_tree_all_data( 'main-menu' );
$options = menu_tree_output( $tree );
The second line is because, as explained in https://groups.drupal.org/node/145064 , "Geek question - Why is there no render array for links in D7?", menus are not render arrays, and need to be converted to such.
So $options
is a render array. I first tested that I understood its structure well enough to loop over it and print the first- and second-level options. Here's my code:
foreach ( $options as $option ) {
$title = $option[ '#title' ];
if ( strlen( $title ) != 0 ) {
print $title . '<BR>';
$sub_options = $option[ '#below' ];
if ( count( $sub_options ) > 0 ) {
foreach ( $sub_options as $sub_option ) {
$sub_title = $sub_option[ '#title' ];
if ( strlen( $sub_title ) != 0 )
print '  ' . $sub_title . '<BR>';
}
}
}
}
This seems to work, displaying the list:
OPTION 1
OPTION 2
Sub-option 2.1
Sub-option 2.2
OPTION 3
Sub-option 3.1
(The above are the names of my test options — not very interesting, I know.)
A problem I had when writing the code is that I couldn't find a spec of exactly what a menu's render array has in it. But from dpm
and the source of menu_tree_output
, I gathered that an option's name is in its #title
element, and that its sub-options are in its #below
element, which is an array.
Actually, that wasn't quite enough. Some options had empty titles which I had to skip: hence my tests on the length of $title
and $sub_title
. But I seemed to be going in the right direction, so I then tried another loop. This one was intended, just as a proof of concept, to add a prefix and a suffix to each list of options: both the first-level list and the second-level lists.
Here's that code:
foreach ( $options as $option ) {
$title = $option[ '#title' ];
if ( strlen( $title ) != 0 ) {
$sub_options = $option[ '#below' ];
if ( count( $sub_options ) > 0 ) {
$sub_options[ '#prefix' ] = 'Level 2 prefix';
$sub_options[ '#suffix' ] = 'Level 2 suffix';
}
}
}
$options[ "#prefix" ] = "Level 1 prefix";
$options[ "#suffix" ] = "Level 1 suffix";
print( drupal_render( $options ) );
Apparently, it's a feature of all render arrays that if you set their #prefix
and #suffix
elements and then render and display them, you'll see the prefix appear before the output and the suffix after it. So I thought this would be a good test. The first-level prefix and suffix do indeed appear, enclosing the menu itself.
But the second-level ones don't. Why? The source of menu_tree_output
contains the line
$element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
which certainly looks as though the #below
elements should become render arrays. So why aren't #prefix
and #suffix
working on them?
By the way, I clear my cache before each test with drush cc all
. If it matters. And my code is running in a page.tpl.php
which I've written from scratch as part of a new theme. So there shouldn't be any interference from an existing theme.
$options
array before rendering it,foreach ( $options as $option ) {
needs to beforeach ( $options as &$option ) {
, and$sub_options = $option[ '#below' ];
needs to be$sub_options = &$option[ '#below' ];