0

How can contextual fields be added to breadcrumb links?

For example, I have a hierarchical taxonomy vocabulary like this:

Animalia
--Chordata
----Vertebrata

On the Vertebrata page, the default breadcrumb looks like this:

Home >> Animalia >> Chordata

Each taxonomy term in the vocabulary has a "common name" field, so for Animalia it would be "animals", and for Chordata it would be "chordates". I would like the breadcrumb to look like this:

Home >> Animalia (animals) >> Chordata (chordates)

A similar example can be seen on this page, where a taxonomic rank field appears after each link in the classification breadcrumbs.

Besides achieving this with taxonomy breadcrumbs, I would be interested to know if a similar solution is achievable for menu breadcrumbs generated by the Menu Breadcrumb module.

2 Answers 2

1

I'm not aware of any contrib module that is capable of adding custom fields as suffix to breadcrumb links.

Fortunately, Drupal 8's modular architecture enables you to add your own breadcrumb builder service with a little bit of custom programming.

You will have to create an own module (within the following example, I'll call it my_module or MyModule respectively; you'll have to replace it accordingly) and add a my_module.services.yml file to its root directory.

Within this file you can define your own breadcrumb builder service. Make sure to let Drupal know, that it is a breadcrumb builder and in which order it should be processed by adding the relevant tags (read more about service tags here):

services:
  my_module.breadcrumb:
    class: Drupal\my_module\Service\MyModuleBreadcrumbBuilder
    arguments: ['@entity.manager']
    tags:
      - { name: breadcrumb_builder, priority: 10001 }

Now that Drupal knows about your breadcrumb builder and will process it fairly early because of the high priority given, you will have to create the according class.

Create a service class MyModuleBreadcrumbBuilder.php in your my_module/src/Service/ folder. Apart from the convention to letting its name end on BreadcrumbBuilder, you can actually name it to your liking. Just make sure to change the class name within the .services.yml file and the follwing example accordingly.

As you are up to creating a taxonomy breadcrumb builder, a good starting point is the TermBreadcrumbBuilder of the taxonomy core module, which we will extend in our example:

namespace Drupal\my_module\Service;

use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\taxonomy\TermBreadcrumbBuilder;

/**
 * My custom breadcrumb builder for Animalia.
 */
class MyModuleBreadcrumbBuilder extends TermBreadcrumbBuilder implements BreadcrumbBuilderInterface {

}

As your requirements seem to apply to a certain taxonomy vocabulary only, you should implement the BreadcrumbBuilderInterface::applies() method to check whether you are on a term page of that vocabulary.

This assures your breadcrumb builder won't run on other entities/vocabularies that don't feature your custom field (I used the ID my_vocabulary for your vocabulary; you will have to replace it accordingly):

use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\taxonomy\TermInterface;

[...]

/**
 * {@inheritdoc}
 */
public function applies(RouteMatchInterface $route_match) {
  return $route_match->getRouteName() == 'entity.taxonomy_term.canonical'
    && ($term = $route_match->getParameter('taxonomy_term')) instanceof TermInterface
    && $term->getVocabularyId() == 'my_vocabulary';
}

The next step is to implementing the BreadcrumbBuilderInterface::build() method, that returns the actual breadcrumb. Here, we'll add your custom field value to the shown parent term link labels.

As we extended the taxonomy breadcrumb builder before, we already have the entity manager and term storage available, which enable us to loading the localized versions of the terms.

Most of the proceedings match the TermBreadcrumbBuilder::build() method, so that I added a comment only where the term field value is added:

use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Link;

[...]

/**
 * {@inheritdoc}
 */
public function build(RouteMatchInterface $route_match) {
  $breadcrumb = new Breadcrumb();
  $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));
  $term = $route_match->getParameter('taxonomy_term');
  $breadcrumb->addCacheableDependency($term);
  $parents = $this->termStorage->loadAllParents($term->id());
  array_shift($parents);

  foreach (array_reverse($parents) as $term) {
    $term = $this->entityManager->getTranslationFromContext($term);

    // The 'magic' happens here by loading the field value and
    // adding it to the link title; alter 'field_common_name' to
    // match your custom field ID.
    $my_field = $term->get('field_common_name');
    $term_name = $term->getName() . (!empty($my_field->getValue()[0]['value']) ? ' (' . $my_field->getValue()[0]['value'] . ')' : '');

    $breadcrumb->addCacheableDependency($term);
    $breadcrumb->addLink(Link::createFromRoute($term_name, 'entity.taxonomy_term.canonical', ['taxonomy_term' => $term->id()]));
  }

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

After enabling the module (or flushing the caches, if you already enabled it before), you should now see your custom breadcrumbs when you open a term of your vocabulary.

4
  • Thank you very much for your generous help. Can I confirm that your last 3 code blocks all belong in the same my_module/src/Service/MyModuleBreadcrumbBuilder.php file? Please can you clarify which parts of your code should be "nested" and which parts should just appear in series (i.e. where do the brackets close in the complete file)? I have tried a few combinations but can't get the module to appear on the Extend page. Also, can you please confirm that there shouldn't be an "s" on the end of "service" in my_module.service.yml?
    – Nick Hope
    Commented Dec 26, 2017 at 13:27
  • Yes they all belong to the same file. I tried giving you some extra info on what each part is doing. Just copy the 'use' parts to the top of your file and the two functions within the class block. Similar to the TermBreadcrumbBuilder class. PS.: Welcome. Spent my Christmas morning coffee with it. ;) Commented Dec 26, 2017 at 13:37
  • @NickHope: The above code example does not cover how to create a custom module, but only what to include once you have created one. Please read the D8 docs on this topic at drupal.org/docs/8/creating-custom-modules. To have your module appear in Extend, you will need at least a my_module.info.yml file, and probably also have to flush your Drupal's caches. The given example requires medium programming skills and at least a basic understanding on how to extend Drupal 8 programmatically. The docs will get you started. And yes, the services.yml file was missing an 's'. Commented Dec 26, 2017 at 14:41
  • Thanks for the extra info. I'm still on a steep Drupal learning curve! I only had to add a simple my_module.info.yml file containing name, type, description, and core values, and now it works beautifully for my taxonomy breadcrumbs. Cheers!
    – Nick Hope
    Commented Dec 26, 2017 at 15:04
0

Here are the components of Mario's module in the accepted answer after updating for Drupal 9.0-9.1 compatibility. This module will still work in Drupal 8.

breadcrumb_extra_field.info.yml

name: Breadcrumb Extra Field
description: 'Add an extra field after terms in a breadcrumb'
package: Custom
type: module
core_version_requirement: ^8.7.7 || ^9

breadcrumb_extra_field.services.yml

services:
  breadcrumb_extra_field.breadcrumb:
    class: Drupal\breadcrumb_extra_field\Service\BreadcrumbExtraFieldBreadcrumbBuilder
    arguments: ['@entity_type.manager', '@entity.repository']
    tags:
      - { name: breadcrumb_builder, priority: 10001 }

src\Service\BreadcrumbExtraFieldBreadcrumbBuilder.php

<?php

namespace Drupal\breadcrumb_extra_field\Service;

use Drupal\Core\Link;
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\taxonomy\TermBreadcrumbBuilder;
use Drupal\taxonomy\TermInterface;

/**
 * My custom breadcrumb builder for the living_things taxonomy.
 */
class BreadcrumbExtraFieldBreadcrumbBuilder extends TermBreadcrumbBuilder implements BreadcrumbBuilderInterface {

    /**
     * {@inheritdoc}
     */
    public function applies(RouteMatchInterface $route_match) {
        return $route_match->getRouteName() == 'entity.taxonomy_term.canonical' && ($term = $route_match->getParameter('taxonomy_term')) instanceof TermInterface && $term->bundle() == 'living_things';
    }

    /**
     * {@inheritdoc}
     */
    public function build(RouteMatchInterface $route_match) {
        $breadcrumb = new Breadcrumb();
        $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));
        $term = $route_match->getParameter('taxonomy_term');
        $breadcrumb->addCacheableDependency($term);
        $parents = $this->termStorage->loadAllParents($term->id());
        array_shift($parents);
        foreach (array_reverse($parents) as $term) {
            $term = \Drupal::service('entity.repository')->getTranslationFromContext($term);
            $my_field = $term->get('field_most_common_name');
            $term_name = $term->getName() . (!empty($my_field->getValue()[0]['value']) ? ' (' . $my_field->getValue()[0]['value'] . ')' : '');
            $breadcrumb->addCacheableDependency($term);
            $breadcrumb->addLink(Link::createFromRoute($term_name, 'entity.taxonomy_term.canonical', ['taxonomy_term' => $term->id()]));
        }
        $breadcrumb->addCacheContexts(['route']);
        return $breadcrumb;
    }
}
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.