Obtener menú enlace hermanos

11

Estoy tratando de crear un menú en Drupal 8 que sea solo enlaces hermanos de la página actual. Entonces, si el menú es:

  • Hogar
  • Padre 1
    • Subparental 1
      • Niño 1
    • Subparental 2
      • Niño 2
      • Niño 3
      • Niño 4
  • Padre 2

Cuando estoy en la página "Niño 3", quiero que se vincule un bloque de menú para que se vea así:

  • Niño 2
  • Niño 3
  • Niño 4

Sé cómo hacer esto en D7, creo, pero me resulta difícil traducir ese conocimiento a D8. ¿Es esto algo que es factible en D8? Y si es así, ¿alguien puede señalarme en la dirección correcta sobre cómo hacerlo?

¡Gracias!

Aclaración: el nivel secundario debe ser variable, de modo que los elementos del menú con diferentes profundidades puedan mostrar a sus hermanos. Entonces, por ejemplo, además de querer un menú para los niños, necesitaría un menú para los padres secundarios y un menú para los padres, etc. Tampoco tengo control sobre / conocimiento de cuán profundo es el menú y si es tan profundo en todas las secciones.

Erin McLaughlin
fuente

Respuestas:

19

Entonces, terminé descubriendo un código que me permitiera hacer esto, creando un bloque personalizado y, en el método de compilación, generando el menú con transformadores agregados. Este es el enlace que utilicé para descubrir cómo obtener el menú en el bloque y agregarle transformadores: http://alexrayu.com/blog/drupal-8-display-submenu-block . Mi build()terminó con este aspecto:

$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'),
  ),
);

El transformador es un servicio, así que agregué un intranet.services.ymlmódulo de mi intranet, señalando la clase que terminé definiendo. La clase tenía tres métodos: removeInactiveTrail()que llamaba getCurrentParent()para obtener el padre de la página en la que el usuario estaba actualmente y stripChildren()que despojaba el menú a solo los hijos del elemento de menú actual y sus hermanos (es decir, eliminaba todos los submenús que no estaban ' t en el camino activo).

Esto es lo que parecía:

/**
 * 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;
}

¿Es esta la mejor manera de hacerlo? Probablemente no. Pero al menos proporciona un punto de partida para las personas que necesitan hacer algo similar.

Erin McLaughlin
fuente
Esto es más o menos lo que hace footermap. +1 por usar el servicio menu.tree.
mradcliffe
2
¿Podría decir qué código debe colocarse en el archivo service.yml? ¿Cómo apuntar una clase desde el archivo service.yml?
siddiq
¿Cómo excluir el enlace del menú principal / s?
Permana
3

Drupal 8 tiene la funcionalidad Bloque de menú integrada en el núcleo, lo único que tiene que hacer es crear un nuevo bloque de menú en la Ui de bloque y configurarlo.

Eso sucede por:

  • Colocar un nuevo bloque y luego seleccionar el menú para el que desea crear un bloque.
  • En la configuración de bloque, debe seleccionar el "Nivel de menú inicial" para que sea 3.
  • También es posible que desee establecer el "Número máximo de niveles de menú para mostrar" en 1 en caso de que solo desee imprimir elementos de menú del tercer nivel.
lauriii
fuente
Desafortunadamente, no puedo estar seguro de en qué nivel estará la página, así que no puedo crear un bloque de menú para ella. También existe la posibilidad de que tenga que ser niveles variables, dependiendo de cuál sea la estructura del sitio.
Erin McLaughlin
menu_block para Drupal 8 actualmente no incluye funcionalidad para seguir el nodo actual, parches en revisión aquí; drupal.org/node/2756675
Christian
OK para uso estático. Pero no para uso dinámico como en "Coloque un bloque en cada página y muestre a los hermanos de la página actual sin importar en qué nivel se encuentre actualmente".
leymannx
3

Establecer la raíz en el enlace actual podría hacer los trucos:

$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
fuente
No, desafortunadamente esto solo muestra niños. Pero no hermanos. OP quiere hermanos.
leymannx
3

Bloque de menú de hermanos

Con la ayuda de la respuesta @Icubes MenuLinkTreeInterface::getCurrentRouteMenuTreeParameters, simplemente podemos obtener la ruta del menú activo de la ruta actual. Teniendo eso también tenemos el elemento del menú principal. Establecer eso como punto de partida MenuTreeParameters::setRootpara construir un nuevo árbol te da el menú de hermanos deseado.

// 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
fuente
Esta solución funcionó a las mil maravillas. :)
Pankaj Sachdeva