Drupal 8 a drobečková navigace flexibilně? Návod na vlastní modul

Drobečková navigace, breadcrumb, je nejenom v Drupalu 8 naprostý bolehlav. Jde o nejobtížněji nastavitelnou část webu. A zatímco v sedmičce si můžete vybrat z několika řešení, Drupal 8 nám na výběr moc nedává. Nejrychlejším a nejpohodlnějším řešením je proto vytvoření jednoduchého vlastního modulu.

Myslím, že si nevybavuji web stavěný na Drupalu, kde by mi drobečková navigace nedala zabrat. Tedy poněkud větší web, než je několik stránek kompletně nalinkovaných do menu. V momentě, kdy potřebujete nastavovat pravidla podle typu obsahu, podle kategorie či jiného členění, máte docela velký problém.

Jak funguje drobečková navigace v Drupalu?

Pro méně zkušené Drupalisty je často záhadou, jak vlastně drobečkovka v Drupalu funguje. Na tom, že dělá obvykle něco jiného, než očekáváte, není nic tajemného. Pokud najde stránku prolinkovanou v hlavním menu, pak ji Drupal zařadí do drobečkové navigace, přičemž zachovává odkaz na titulní stranu webu a strukturu zanoření stránky v hlavním menu.

V případě, že chcete cokoli sofistikovanějšího, máte smůlu. Existuje proto hned celá řada doplňkových modulů, které drobečkovou navigaci v Drupalu řeší. Stačí si na stránkách drupal.org zadat hledání modulů majících v názvu slovo breadcrumb.

V Drupalu 7 používám nejčastěji modul Custom Breadcrumbs. Přestože není nejjednodušší na pochopení, nabízí dostatečnou flexibilitu a jen málokdy jsem musel sáhnout po vlastním kódu.

Abych pravdu řekl, v Drupalu 8 jsem s hledáním vhodného modulu pro breadcrumb seknul hned v začátcích. Custom Breadcrumbs v této verzi není, a navíc ani není pokryt kompletní bezpečnostní kontrolou. Také jsem si uvědomil, že drobečková navigace není něco, co bychom já nebo klient potřebovali na webu neustále měnit. Důležité je správné nastavení na začátku a pak to funguje.

Bohatě mi tedy stačí, když je nyní drobečková navigace samostatný blok a nemusím se spoléhat jen na její umístění v šabloně. Samotný obsah drobečkové navigace jsem začal řešit na míru napsaným modulem. Jeho kostru vám v tomto článku nabízím spolu s několika tipy, jak do drobečkovky dostat potřebné hodnoty.

Vlastní modul pro Drupal 8 a drobečková navigace

Začněte tím, že si připravíte složku modules/custom/muj_breadcrumb. V ní vytvořte soubor s definicí modulu, tedy, muj_breadcrumb.info.yml. Informační soubor by měl mít tento minimální obsah:

name: Muj Breadcrumb
type: module
description: Nastaveni drobeckove navigace
core: 8.x
package: Custom

Protože určitě dbáte na to, abyste vše měli co nejlépe vyřešeno, určitě k modulu poskytnete i nápovědu (přestože se tam uživatel zřejmě mnoho nedočte). Vytvořte tedy soubor muj_breadcrumb.module s následujícím obsahem:

<?php

use Drupal\Core\Routing\RouteMatchInterface;

function muj_breadcrumb_help($route_name, RouteMatchInterface $route_match) {
 switch ($route_name) {
   case 'help.page.muj_breadcrumb':
     $output = '';
     $output .= '<h3>' . t('My breadcrumbs') . '</h3>';
     $output .= '<p>' . t('My custom breadcrumbs for this website') . '</p>';
     return $output;
   default:
 }
}

Jde vlastně o jedinou funkci, jejímž smyslem je zobrazit informační text poté, co se uživatel v administračním menu přepne do nápovědy a rozklepne si informace k tomuto modulu.

Poslední soubor, který je potřeba vytvořit přímo ve složce s modulem, je muj_breadcrumb.services.yml. Zde uvedete definici služby, která bude breadcrumb vytvářet. Ta může mít nějakou prioritu, takže v případě potřeby s ní přebijete výchozí či jiné moduly pro vytváření drobečkové navigace v Drupalu 8. Obsah souboru je následující:

services:
 muj_breadcrumb.breadcrumb:
   class: Drupal\muj_breadcrumb\Breadcrumb\MujBreadcrumbBuilder
   tags:
    - { name: breadcrumb_builder, priority: 10001 }

Jak vidíte, používám prioritu 10001, což by mělo být dostatečně vysoké číslo, aby Drupal dal při kompletaci drobečkové navigaci přednost mému modulu. Výchozí priorita u modulů v jádře Drupalu by totiž měla být nulová.

Služba generující drobečkovou navigaci

Kostru modulu s definicí jeho samotného i s definicí služby pro sestavení breadcrumbu jste připravili, nyní je potřeba vytvořit kód této služby. Ve složce s modulem vytvořte podsložku s názvem src/Breadcrumb. Nezapomeňte dodržet velké písmenko.

V této složce připravte poslední soubor, který v rámci modulu budete potřebovat: MujBreadcrumbBuilder.php. Z názvu modulu bude vycházet i název třídy v souboru. Základní kostra by měla vypadat následovně:

<?php

namespace Drupal\muj_breadcrumb\Breadcrumb;

use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Link;


class MujBreadCrumbBuilder implements BreadcrumbBuilderInterface {

 public function applies(RouteMatchInterface $attributes) {
   $parameters = $attributes->getParameters()->all();
   if (isset ($parameters['node']) && !empty ($parameters['node'])) {
     return TRUE;
   }
 }

 public function build(RouteMatchInterface $route_match) {

 }

}

Máte zde vlastně dvě funkce. První vyhodnocuje, zda bude drobečková navigace tvořená tímto modulem uplatněna. A pokud vrátí TRUE, zavolá se funkce build(), která obsah drobečkové navigace vytvoří.

Příklad ukazuje, že drobečkovka se tímto modul bude aplikovat jen v případě, že je zobrazen nějaký obsahový uzel. Pokud byste ji chtěli postavit i pro stránky tvořené Views nebo jiným modulem, vraťte prostě v applies() vždy TRUE a budete mít drobečkovou navigaci komplet ve své režii.

Nyní tedy to hlavní. Ve funkci build() vytvořte nový Breadcrumb a přidejte mu odkaz na titulní stranu webu. Abyste neskončili na tom, že všechny stránky webu budou mít stejný obsah drobečkové navigace, musíte do cache přidat kontext s tím, že se bude drobečkovka uchovávat pro každou routu zvlášť. Následně pošlete sestavenou drobečkovou navigaci z funkce ven do světa.

 public function build(RouteMatchInterface $route_match) {
   $breadcrumb = new Breadcrumb();
   $breadcrumb->addLink(Link ::createFromRoute('Domů', '<front>'));

   $breadcrumb->addCacheContexts(['route']);
   return $breadcrumb;
 }

Nyní tomuto řešení dodejte na praktičnosti v tom, že do drobečkovky doplníte název aktuálního obsahového uzlu. Jen jako text, bez odkazu.

Získejte tedy parametry z aktuální adresy (routy) a načtěte z nich aktuální uzel. Pokud v applies() nemáte omezení jen na uzly, je třeba ještě ověřit, že se vám uzel opravdu podařilo získat. Nejlépe podmínkou if.

V ní pak stačí jedním příkazem drobečkovku obohatit o zobrazení textu aktuálního nadpisu.

 public function build(RouteMatchInterface $route_match) {
   $breadcrumb = new Breadcrumb();
   $breadcrumb->addLink(Link ::createFromRoute('Domů', '<front>'));

   $parameters = \Drupal::routeMatch()->getParameters()->all();

   $node = \Drupal::routeMatch()->getParameter('node');

   if ($node){
     $breadcrumb->addLink(Link ::createFromRoute($node->getTitle(), '<nolink>'));
   }

   $breadcrumb->addCacheContexts(['route']);
   return $breadcrumb;
 }

Dosavadní řešení by vám nabídl i Drupal sám, pokud by byl každý uzel nalinkovaný do menu. Ale pomůže, pokud tomu tak není. Vezměte si praktičtější příklad. Na webu máte například obsah typu galerie, který zobrazuje nějaké fotografie a seznam těchto obsahových záznamů pro galerie máte na stránce /galerie, kterou jste vytvořili pomocí modulu Views. Pohled jste si pojmenovali jako fotogalerie. A nyní chcete, aby každý záznam v galerii měl takovouto drobečkovou navigaci:

  • Domů
  • Fotogalerie (s odkazem na stránku /galerie definovanou ve View)
  • Název právě prohlížené galerie (bez odkazu)

Kromě toho jste si přes Views udělali pohled nazvaný novinky, který na definované adrese /novinky zobrazuje komplet všechny uzly typu článek (se strojovým názvem article, jak to definuje výchozí instalace Drupalu 8). Pro ně byste chtěli mít drobečkovou navigaci v následující podobě:

  • Domů
  • Novinky (s odkazem na stránku /novinky definovanou ve View)
  • Nadpis novinky (bez odkazu)

Kromě toho máte na webu ještě výchozí typ obsahu Stránka, který bude mít v drobečkové navigaci pouze odkaz na titulku a textový nadpis jako dosud.

Potřebujete se tedy rozhodnout, co do drobečkové navigace přidáte v závislosti na typu obsahu, s tím že dosavadní text s nadpisem stránky tam bude vždy. Stačí tedy zjistit typ uzlu a použít konstrukci switch…case. Výsledek:

 public function build(RouteMatchInterface $route_match) {
   $breadcrumb = new Breadcrumb();
   $breadcrumb->addLink(Link ::createFromRoute('Domů', '<front>'));

   $parameters = \Drupal::routeMatch()->getParameters()->all();

   $node = \Drupal::routeMatch()->getParameter('node');

   if ($node){
     $node_type = $node->bundle();
     switch ($node_type) {       
       case 'galerie':
         $breadcrumb->addLink(Link ::createFromRoute('Fotogalerie', 'view.fotogalerie.page_1'));
         break;
       case 'article':
         $breadcrumb->addLink(Link ::createFromRoute('Novinky', 'view.novinky.page_1'));
         break;
     }
     $breadcrumb->addLink(Link ::createFromRoute($node->getTitle(), '<nolink>'));
   }

   $breadcrumb->addCacheContexts(['route']);
   return $breadcrumb;
 }

Všimněte si, že funkce createFromRoute() má jako druhý atribut strojový název routy. V případě Views je to vždy tvořeno řetězcem ve tvaru view.nazev_pohledu.page_cislo-stránky-v-pohledu.

Drobečková navigace pro termíny v kategorii

Úprava drobečkové navigace pro uzly v Drupalu 8 asi bude nejčastějším úkolem, který budete řešit. Někdy ale budete potřebovat i její nastavení pro stránky vypisující nějaké termíny kategorií.

Řešení je jednoduché. V applies() musíte vrátit TRUE v případě, že je v parametrech nastaven a není prázdný termín kategorie: $parameters['taxonomy_term']. Případně tedy vracet TRUE vždy, jak jsem zmínil o něco výše.

Ve funkci build() se asi budete muset rozhodnout, zda je zobrazený termín z konkrétního slovníku a v takovém případě pak sestrojit drobečkovou navigaci s názvem aktuálního termínu, kterému předcházejí klikací názvy termínů nadřazených. Výsledek by byl následující:

    if (isset($parameters['taxonomy_term'])) {
       $term = $parameters['taxonomy_term'];
   
       if ($term->getVocabularyId() == 'zbozi') {
         $parents = \Drupal::service('entity_type.manager')->getStorage("taxonomy_term")->loadAllParents($term->id());
         foreach (array_reverse($parents) as $parent){
           $breadcrumb->addLink(Link ::createFromRoute($parent->getName(),'entity.taxonomy_term.canonical', array('taxonomy_term' => $parent->id())));
         }
   
         $breadcrumb->addLink(Link ::createFromRoute($term->getName(),'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id())));
       }
     }

Podívejte se, jak se předává konkrétní termín pro sestrojení odkazu funkcí createFromRoute().

Vlastní drobečkovka pro View

V případě, že vracíte v applies() hodnotu TRUE vždy, budete asi nuceni vytvořit správnou drobečkovou navigaci i pro jednotlivé stránky tvořené modulem Views. V takovém případě je třeba obohatit funkci build() o následující konstrukci:

   if (isset($parameters['view_id']) && isset($parameters['display_id'])){
     $view = \Drupal\views\Views::getView($parameters['view_id']);
     $view->setDisplay($parameters['display_id']);
     $title = $view->getTitle();
     $route = 'view.'.$parameters['view_id'].'.'.$parameters['display_id'];
     $breadcrumb->addLink(Link ::createFromRoute($title, $route));
   }

Kategorie uzlu v drobečkové navigaci

Poslední příklad z praxe, který mě napadl, je vložení odkazu na termín kategorie, který je přiřazen u nějakého uzlu. Řekněme, že články uživatel zařazuje do jedné z kategorií napojených v políčku field_typ_aktuality.

Z předchozího návodu víte, jak udělat drobečkovku jen pro uzly typu article, stejně tak víte, jak udělat odkaz na termín v kategorii. Stačí to spojit se získáním ID termínu v políčku daného uzlu a načtením termínu pro získání jeho názvu. Výsledek pak bude další položkou pro zmíněnou konstrukci switch:

        case 'article':
          $term = \Drupal\taxonomy\Entity\Term::load($node->get('field_typ_aktuality')->target_id);
          $breadcrumb->addLink(Link ::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id()) ));
          break;

Tyto příklady by vám tedy měly pokrýt 99 % případů, na které v kombinaci Drupal 8 a sestrojení drobečkové navigace narazíte. Věřím, že vás relativní množství kódu v tomto článku neodradilo. Pokládám to stále za nejrychlejší řešení pro drobečkovou navigaci dle zadání webu.

Kdyby vás napadl příklad, který moje ukázka nepokrývá, zkuste napsat do komentářů. Pokud to půjde, vymyslíme spolu nějaké řešení.

Tagy

Buďme ve spojení, přihlaste se k newsletteru

Odesláním formuláře souhlasíte s podmínkami zpracováním osobních údajů. 
Více informací v Ochrana osobních údajů.

Autor článku: Jan Polzer

Tvůrce webů z Brna se specializací na Drupal, WordPress a Symfony. Acquia Certified Developer & Site Builder. Autor několika knih o Drupalu.
Web Development Director v Lesensky.cz. Ve volných chvílích podnikám výlety na souši i po vodě. Více se dozvíte na polzer.cz a mém LinkedIn profilu.

Komentáře k článku

Přidat komentář

Odesláním komentáře souhlasíte s podmínkami Ochrany osobních údajů

reklama
Moje kniha o CMS Drupal

 

Kniha 333 tipů a triků pro Drupal 9


Více na KnihyPolzer.cz

Sledujte Maxiorla na Facebooku

Maxiorel na Facebooku

Hosting pro Drupal a WordPress

Hledáte český webhosting vhodný nejenom pro redakční systém Drupal? Tak vyzkoušejte Webhosting C4 za 1200 Kč na rok s doménou v ceně, 20 GB prostoru a automatické navyšováním o 2 GB každý rok. Podrobnosti zde.

@maxiorel na Twitteru

Maxiorel na Twitteru