Obter irmãos de link de menu

11

Estou tentando criar um menu no Drupal 8 que é apenas links irmãos da página atual. Portanto, se o menu for:

  • Casa
  • Pai 1
    • Subparental 1
      • Criança 1
    • Subparental 2
      • Criança 2
      • Criança 3
      • Criança 4
  • Pai 2

Quando estou na página "Filho 3", quero um bloco de menu com o seguinte link:

  • Criança 2
  • Criança 3
  • Criança 4

Eu sei como fazer isso no D7, acho, mas estou tendo dificuldade em traduzir esse conhecimento para o D8. Isso é algo que é factível no D8? E se for, alguém pode me indicar a direção certa de como fazê-lo?

Obrigado!

Esclarecimento: O nível filho precisa ser variável, para que itens de menu com diferentes profundidades possam exibir seus irmãos. Por exemplo, além de querer um cardápio para as crianças, eu precisaria de um cardápio para os pais secundários e de um cardápio para os pais e assim por diante. Também não tenho controle sobre / conhecimento de quão profundo é o menu e se ele é tão profundo em todas as seções.

Erin McLaughlin
fonte

Respostas:

19

Então, acabei descobrindo algum código que me permitia fazer isso, criando um bloco personalizado e, no método build, exibindo o menu com os transformadores adicionados a ele. Este é o link que eu usei para descobrir como obter o menu no bloco e adicionar transformadores a ele: http://alexrayu.com/blog/drupal-8-display-submenu-block . Meu build()acabou ficando assim:

$menu_tree = \Drupal::menuTree();
$menu_name = 'main';

// Build the typical default set of menu tree parameters.
$parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);

// Load the tree based on this set of parameters.
$tree = $menu_tree->load($menu_name, $parameters);

// Transform the tree using the manipulators you want.
$manipulators = array(
  // Only show links that are accessible for the current user.
  array('callable' => 'menu.default_tree_manipulators:checkAccess'),
  // Use the default sorting of menu links.
  array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
  // Remove all links outside of siblings and active trail
  array('callable' => 'intranet.menu_transformers:removeInactiveTrail'),
);
$tree = $menu_tree->transform($tree, $manipulators);

// Finally, build a renderable array from the transformed tree.
$menu = $menu_tree->build($tree);

return array(
  '#markup' => \Drupal::service('renderer')->render($menu),
  '#cache' => array(
    'contexts' => array('url.path'),
  ),
);

O transformador é um serviço, então adicionei um intranet.services.ymlao meu módulo da intranet, apontando para a classe que acabei definindo. A classe tinha três métodos: removeInactiveTrail()chamado getCurrentParent()para obter o pai da página em que o usuário estava atualmente e stripChildren()que reduzia o menu apenas para os filhos do item de menu atual e seus irmãos (por exemplo: removeu todos os submenus que não estavam ' t na trilha ativa).

Isto é o que parecia:

/**
 * Removes all link trails that are not siblings to the active trail.
 *
 * For a menu such as:
 * Parent 1
 *  - Child 1
 *  -- Child 2
 *  -- Child 3
 *  -- Child 4
 *  - Child 5
 * Parent 2
 *  - Child 6
 * with current page being Child 3, Parent 2, Child 6, and Child 5 would be
 * removed.
 *
 * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
 *   The menu link tree to manipulate.
 *
 * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
 *   The manipulated menu link tree.
 */
public function removeInactiveTrail(array $tree) {
  // Get the current item's parent ID
  $current_item_parent = IntranetMenuTransformers::getCurrentParent($tree);

  // Tree becomes the current item parent's children if the current item
  // parent is not empty. Otherwise, it's already the "parent's" children
  // since they are all top level links.
  if (!empty($current_item_parent)) {
    $tree = $current_item_parent->subtree;
  }

  // Strip children from everything but the current item, and strip children
  // from the current item's children.
  $tree = IntranetMenuTransformers::stripChildren($tree);

  // Return the tree.
  return $tree;
}

/**
 * Get the parent of the current active menu link, or return NULL if the
 * current active menu link is a top-level link.
 *
 * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
 *   The tree to pull the parent link out of.
 * @param \Drupal\Core\Menu\MenuLinkTreeElement|null $prev_parent
 *   The previous parent's parent, or NULL if no previous parent exists.
 * @param \Drupal\Core\Menu\MenuLinkTreeElement|null $parent
 *   The parent of the current active link, or NULL if not parent exists.
 *
 * @return \Drupal\Core\Menu\MenuLinkTreeElement|null
 *   The parent of the current active menu link, or NULL if no parent exists.
 */
private function getCurrentParent($tree, $prev_parent = NULL, $parent = NULL) {
  // Get active item
  foreach ($tree as $leaf) {
    if ($leaf->inActiveTrail) {
      $active_item = $leaf;
      break;
    }
  }

  // If the active item is set and has children
  if (!empty($active_item) && !empty($active_item->subtree)) {
    // run getCurrentParent with the parent ID as the $active_item ID.
    return IntranetMenuTransformers::getCurrentParent($active_item->subtree, $parent, $active_item);
  }

  // If the active item is not set, we know there was no active item on this
  // level therefore the active item parent is the previous level's parent
  if (empty($active_item)) {
    return $prev_parent;
  }

  // Otherwise, the current active item has no children to check, so it is
  // the bottommost and its parent is the correct parent.
  return $parent;
}


/**
 * Remove the children from all MenuLinkTreeElements that aren't active. If
 * it is active, remove its children's children.
 *
 * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
 *   The menu links to strip children from non-active leafs.
 *
 * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
 *   A menu tree with no children of non-active leafs.
 */
private function stripChildren($tree) {
  // For each item in the tree, if the item isn't active, strip its children
  // and return the tree.
  foreach ($tree as &$leaf) {
    // Check if active and if has children
    if ($leaf->inActiveTrail && !empty($leaf->subtree)) {
      // Then recurse on the children.
      $leaf->subtree = IntranetMenuTransformers::stripChildren($leaf->subtree);
    }
    // Otherwise, if not the active menu
    elseif (!$leaf->inActiveTrail) {
      // Otherwise, it's not active, so we don't want to display any children
      // so strip them.
      $leaf->subtree = array();
    }
  }

  return $tree;
}

Esse é o melhor jeito de fazer isso? Provavelmente não. Mas pelo menos fornece um ponto de partida para pessoas que precisam fazer algo semelhante.

Erin McLaughlin
fonte
Isso é basicamente o que o rodapé faz. +1 para usar o serviço menu.tree.
31416 mradcliffe
2
Você poderia dizer qual código deve ser colocado no arquivo service.yml? Como apontar uma classe do arquivo service.yml?
siddiq
Como excluir o link do menu pai / s?
Permana
3

O Drupal 8 tem a funcionalidade do Bloco de menus integrada no núcleo. O que você precisa fazer é criar um novo bloco de menus na interface do usuário do bloco e configurá-lo.

Isso acontece por:

  • Colocando um novo bloco e selecionando o menu para o qual você deseja criar um bloco.
  • Na configuração do bloco, você deve selecionar o "Nível inicial do menu" como 3.
  • Você também pode definir o "Número máximo de níveis de menu a serem exibidos" para 1, caso deseje imprimir apenas itens de menu do terceiro nível.
lauriii
fonte
Infelizmente, não sei ao certo em que nível a página estará; portanto, não posso apenas criar um bloco de menu para ela. Também há a possibilidade de que seja necessário ter níveis variáveis, dependendo do que a estrutura do site acaba sendo.
Erin McLaughlin
O menu_block do Drupal 8 atualmente não inclui funcionalidade para seguir o nó atual, correções em revisão aqui; drupal.org/node/2756675
Christian
OK para uso estático. Mas não para uso dinâmico, como em "Coloque um bloco em todas as páginas e mostre os irmãos da página atual, independentemente do nível em que você está atualmente".
Leymannx #
3

Definir a raiz no link atual pode fazer os truques:

$menu_tree = \Drupal::menuTree();
$menu_name = 'main';

$parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
$currentLinkId = reset($parameters->activeTrail);
$parameters->setRoot($currentLinkId);
$tree = $menu_tree->load($menu_name, $parameters);

// Transform the tree using the manipulators you want.
$manipulators = array(
  // Only show links that are accessible for the current user.
  array('callable' => 'menu.default_tree_manipulators:checkAccess'),
  // Use the default sorting of menu links.
  array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
);
$tree = $menu_tree->transform($tree, $manipulators);
lcube
fonte
Infelizmente, isso só mostra crianças. Mas não irmãos. OP quer irmãos.
Leymannx #
3

Bloco de menu Irmãos

Com a ajuda da resposta do @Icubes MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters, podemos simplesmente obter a trilha do menu ativo da rota atual. Tendo isso, também temos o item de menu principal. Definir isso como ponto de partida MenuTreeParameters::setRootpara criar uma nova árvore fornece o menu de irmãos desejado.

// Enable url-wise caching.
$build = [
  '#cache' => [
    'contexts' => ['url'],
  ],
];

$menu_name = 'main';
$menu_tree = \Drupal::menuTree();

// This one will give us the active trail in *reverse order*.
// Our current active link always will be the first array element.
$parameters   = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
$active_trail = array_keys($parameters->activeTrail);

// But actually we need its parent.
// Except for <front>. Which has no parent.
$parent_link_id = isset($active_trail[1]) ? $active_trail[1] : $active_trail[0];

// Having the parent now we set it as starting point to build our custom
// tree.
$parameters->setRoot($parent_link_id);
$parameters->setMaxDepth(1);
$parameters->excludeRoot();
$tree = $menu_tree->load($menu_name, $parameters);

// Optional: Native sort and access checks.
$manipulators = [
  ['callable' => 'menu.default_tree_manipulators:checkNodeAccess'],
  ['callable' => 'menu.default_tree_manipulators:checkAccess'],
  ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
];
$tree = $menu_tree->transform($tree, $manipulators);

// Finally, build a renderable array.
$menu = $menu_tree->build($tree);

$build['#markup'] = \Drupal::service('renderer')->render($menu);

return $build;
leymannx
fonte
Esta solução funcionou como um encanto. :)
Pankaj Sachdeva