3

Unlike other elements, the base twig suggestions for menus are only "menu.html.twig" and "menu-[menu_name].html.twig".

I am aware that I can generate additional suggestions through the hook THEME_theme_suggestions_menu_alter, but the $variables array does not contain any useful data. Case in point, I want to retrieve the current region, is it possible?

  • If you want to get region, it is possible but not in that template. You have to use the block template for that menu and load the block object and get region from there. Or you can use hook_block_view_alter, depends on your use case. – user21641 Jan 29 '16 at 12:30
  • The use case is to use the same twig template for all menus inside a given region (sidebar). – Filipe Miguel Fonseca Jan 29 '16 at 13:08
  • Hm, in that case I would use that aforementioned hook and add theme suggestion into the build array. – user21641 Jan 29 '16 at 13:17
2

There are a couple of functions that seem like they can help here:

function hook_theme_suggestions_HOOK_alter

function hook_theme_suggestions_alter

This blog post has a useful (if a little bit hacky) way of adding to the template suggestions based on block ID.

http://kristiankaa.dk/article/drupal8-region-specific-menu-theme-hook-suggestion

He suggests using hook_preprocess_block() to add the block ID to the variables array and then using hook_theme_suggestions_HOOK_alter() to add that ID to the theme suggestions for the menus.

/**
 * Implements hook_preprocess_block().
 */
function THEMENAME_preprocess_block(&$variables) {
  $variables['content']['#attributes']['block'] = $variables['attributes']['id'];
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 */
function THEMENAME_theme_suggestions_menu_alter(array &$suggestions, array $variables) {
  // Remove the block and replace dashes with underscores in the block ID to
  // use for the hook name.
  if (isset($variables['attributes']['block'])) {
    $hook = str_replace(array('block-', '-'), array('', '_'), $variables['attributes']['block']);
    $suggestions[] = $variables['theme_hook_original'] . '__' . $hook;
  }
}

So you'll end up with an additional menu--MENU-NAME--BLOCK-NAME.html.twig template possibility. It's a neat trick and works well!

  • does not work for drupal 8.2.x – GiorgosK Feb 28 '17 at 9:46
  • It does work... Currently using on a live production site running 8.2.4. You need to make sure you rebuild the twig cache... Or better still turn it off for development. drupal.org/node/2598914 – featherbelly Mar 1 '17 at 16:00
  • Getting Notice: Array to string conversion in THEMENAME_theme_suggestions_menu_alter() (line 16 of themes/custom/THEMENAME/THEMENAME.theme). – Benedikt Dec 22 '17 at 10:14
  • Do a web search for "Notice: Array to string conversion" this may help you fix this... There are loads of threads on SO. It means you are taking an array variable and using it like it is a string. – featherbelly Jan 6 '18 at 18:51
2

Confirming $variables['attributes'] is always empty in 8.2.x, so there's insufficient contextual info to form a new suggestion

I solved by installing the menu block module, which adds an field for Theme Hook Suggestion for the menu.html.twig into the edit screen for each menu block.

2

All steps described here work for me with some minor modifications.

GOAL: show main menu in both header and footer region, but use a different template file for both (based on their region). PREMISE: your theme has regions (like header and footer) and you have used the drupal block system to place the main menu in both your header as in your footer.

I did this in mytheme.theme file: Dont forget to add the Block use statement in the beginning of your .theme file:

use Drupal\block\Entity\Block;

Now here's the magic for region based menu templates. Also inside your .theme file:

/**
 * Implements hook_preprocess_HOOK().
 *
 * Pass block region value to content so this can be used in
 * MYTHEME_theme_suggestions_menu_alter.
 */
function MYTHEME_preprocess_block(&$variables) {
  if (isset($variables['elements']['#id'])) {
    $region = Block::load($variables['elements']['#id'])->getRegion();
    $variables['content']['#attributes']['region'] = $region;
  }
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 *
 * Provide region based menu suggestions.
 */
function MYTHEME_theme_suggestions_menu_alter(&$suggestions, array $variables) {
  if (isset($variables['attributes']['region'])) {
    $suggestions[] = 'menu__' . $variables['menu_name'] . '__' . $variables['attributes']['region'];
  }
}

RESULT when inspecting the main menu inside the header:

<!-- FILE NAME SUGGESTIONS:
* menu--main.html.twig
* menu--main--header.html.twig
* menu.html.twig
--> 

RESULT when inspecting the main menu inside the footer:

<!-- FILE NAME SUGGESTIONS:
* menu--main.html.twig
* menu--main--footer.html.twig
* menu.html.twig
--> 
  • This worked for my situation. I just remove the . $variables['menu_name'] . '__' portion of the code as I just wanted to theme all menus that were in specific regions. – ACanadianCoder Nov 20 at 16:34
1

You can't get any extra info in suggestions alter, like context. But it's possible to inject suggestions when you render any thing in Drupal 8.

Normally you would do something like this:

$variables['menu'] = [
  '#theme' => 'menu',
  ...
];

Code like this, will probably be inside a menu block, but it could be anywhere.

What you can do to achieve your goal is to change the theme call:

$variables['menu'] = [
  '#theme' => 'menu__header',
  ...
];

Doing the above will add the header suggestion to menu template. I'm not sure how to best accomplish the above.

  1. You should subclass the menu block and change the code a bit
  2. I'm not sure that you can get the region the block is placed in on redner. Otherwise you can add the suggestion as a setting which is configured on the block along with the other menu settings.
  • I played a bit with your suggestion, any idea why in SystemMenuBlock there's no access to the machine name of the block currently being rendered? That would allow to load the entity and get the region... – Filipe Miguel Fonseca Feb 10 '16 at 12:09
  • You should be able to get with either from the configuration or some of the internal methods on the block – googletorp Feb 12 '16 at 10:40
0

Basically the workaround suggested by featherbelly does work on a Drupal 8.2.7, but it's really hacky and the attributes possibly will also get rendered unless you explicitly unset them again. If they do get rendered this will cause invalid markup.

Using menu_block as suggested by user73304 is way the better option here and it gives you full control of which template is used for any menu via the UI, which is great and better than hacky code that might not work anymore in future versions.

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.