How to create a custom block in Drupal 8 to display both Node and taxonomy?

innovation and technology

Challenge: You cannot do a View to list both node and taxonomy as the same entity because they are different, but in my front page I have a block of 4 elements where I want to promote 3 Taxonomies and 1 Service Article as part of the same block. I know you would ask me, why do you want to get a such a silly Challenge like that? Because I was locked at home during the Lockdown and I want to create an exercise to try to do something new, never done before. Please look at the code below and I will show you how I did it.

First you need to create a class that extends BlockBase inside your \Plugin\Block, you can look at my example below:

class MoreServiceBlock extends BlockBase implements ContainerFactoryPluginInterface {

The most important point to understand is that my block needs to run some query into the DataBase so I need to re-use my maria_custom.service as service so that I can use this Class for building my 4 elements:

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    /** @var RouteMatchInterface $route_match */
    $route_match = $container->get('current_route_match');
    /** @var MariaCustomService $custom_service */
    $custom_service = $container->get('maria_custom.service');
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $route_match,
      $custom_service
    );
  }

The most important function inside your Plugin is build(), you can look below to an example on how to implement it, you can download the entire custom module from the Github project repository:

  /**
   * {@inheritdoc}
   */
  public function build() {

    $more_services = [];

    if (!empty($this->configuration['promoted_services'])) {
      $promoted_nids = explode(',', $this->configuration['promoted_services']);
    }
    else {
      $promoted_nids = [];
    }

    if (!empty($this->configuration['promoted_terms'])) {
      $promoted_tids = explode(',', $this->configuration['promoted_terms']);
    }
    else {
      $promoted_tids = [];
    }

    // It should go through this page only when the Route Match is not a Node Service.
    $tot_promoted = count($promoted_tids) + count($promoted_nids);
    if ($tot_promoted > (self::MAX_ELEMENTS-1)) {

      if (!empty($promoted_tids)) {
        $tags_array = $this->getServicesDetailsByTid($promoted_tids);
      }
      else {
        $tags_array = [];
      }

      if (!empty($promoted_nids)) {
        $special_services = $this->getSpecialServicesByNIDs($promoted_nids, count($promoted_nids)+1);
      }
      else {
        $special_services = [];
      }

      $more_services = $this->getMoreServices($tags_array, $special_services);
    }

    if (!empty($more_services)) {
      $build = [
        '#theme' => 'maria_custom_service_block',
        '#more_services' => $more_services,
      ];
      $build['#cache']['max-age'] = 0;
    }
    else {
      $markup = $this->t('More service block did not find any services.');
      $build = [
        '#markup' => $markup,
      ];
    }

    return $build;
  }

You can notices these simple facts:

  • Block Configuration stores the 2 lists: 'promoted_services' and  'promoted_terms'
  • The total items must be at least 4
  • $build['#cache']['max-age'] = 0; Only for test purposed
  • On production you have to comment out that line to cache the Block
  • Taxonomy Terms are loaded by getServicesDetailsByTid()
  • and Service Nodes are loaded by getSpecialServicesByNIDs()
  • getMoreServices() combine these 2 Lists to generate 1 data array
  • $more_services is rendered by a custom theme, which renders the data.

In your custom module try always to reuse code when possible, in my example I load Terms and Nodes without writing any query, I used Dependency Injection to include all common functions from my Custom Service ($this->customService) which is passed to my custom Block class using the the Constructor:

  public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match, MariaCustomService $customService) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->route_match = $route_match;
    $this->customService = $customService;
  }

So the 2 functions look like:

  private function getServicesDetailsByTid($my_tids, $limit = 4)
  {
    $result = [];
    $key = 1;
    foreach ($my_tids as $tid) {
      $more_service = $this->customService->getServiceDetails($tid);
      if (!empty($more_service) && $key < ($limit + 1)) {
        $more_service['key'] = $key;
        $result[] = $more_service;
        $key++;
      } elseif ($key > $limit) {
        break;
      }
    }

    return $result;
  }

  private function getSpecialServicesByNIDs($nids, $start_key = 1)
  {
    $special_services = [];
    $key = $start_key;
    foreach ($nids as $nid) {
      $service_item = $this->customService->getSpecialService($nid);
      if (!empty($service_item)) {
        $service_item['key'] = $key;
        $key++;
        $special_services[] = $service_item;
      }
    }
    return $special_services;
  }

 

Lastly, you need to render the data which in my case is the merge result from these 2 arrays generated by getServicesDetailsByTid() and getSpecialServicesByNIDs() they will be passed to the theme and they look like:

Array
(
    [0] => Array
        (
            [tid] => 9
            [key] => 1
            [name] => 'System (CMS)'
            [image] => '/sites/default/files/styles/medium/public/service/website-maintenance-concept.jpg'
            [caption] => 'Drupal'
            [alt] => 'website maintenance concept'
            [title] => 'HTML, CSS, PHP CMS Systems'
            [href] => '/services/system-cms'
            [description] => 'The great advantage of using Drupal is that is not just free to download, but it really helps to solve real problems and simplify the build of a complex CMS.'

        )

    [1] => Array
        (
            [tid] => 22
            [key] => 2
            [name] => 'Multi-Language'
            [image] => '/sites/default/files/styles/medium/public/service/multi-language-translator.jpg?'
            [caption] => 'Multilingual'
            [alt] => 'multi language translator'
            [title] => 'Translate Drupal in multiple languages'
            [href] => '/services/multi-language'
            [description] => 'Drupal is a very powerful tool to set up multilingual web sites: Internationalisation module helps to easily translate content in different languages.'

        )

    [2] => Array
        (
            [nid] => 14
            [key] => 3
            [name] => 'Drupal Best CMS for SEO'
            [image] => '/sites/default/files/styles/medium/public/services/seo-search-engine-optimization.jpg'
            [caption] => 'Keywords'
            [alt] => 'seo search engine optimization'
            [title] => 'Keywords SEO Drupal CMS'
            [href] => '/drupal-best-cms-seo'
            [description] => 'Read our article on how to use and configure Drupal to use Simple XML sitemap, Meta Tags, Keywords, ALT and titles on images.'
        )

    [3] => Array
        (
            [tid] => 20
            [key] => 4
            [name] => 'Training & Consultation'
            [image] => '/sites/default/files/styles/medium/public/service/teamwork-brainstorming.jpg'
            [caption] => 'Support'
            [alt] => 'teamwork brainstorming meeting and new startup project in workplace'
            [title] => 'How to startup a new project in your workplace'
            [href] => '/services/training-consultation'
            [description] => 'Groups organise, meet, and work on projects based on interest or geographic location. It\'s a great way to get involved, learn more and get support.'

        )

)

As you can see the data set results contains 3 Taxonomies [tid] => 9, [tid] => 22 and [tid] => 20 and the 3rd element is a node [nid] => 14. You can see the result of this code into practise if you look at my home then you will see these 4 elements:

  1. [caption] => 'Drupal'
  2. [caption] => 'Multilingual'
  3. [caption] => 'Keywords'
  4. [caption] => 'Support'

The caption, title, image alt tile and href, description are loaded from inside the Custom Service which has another article that explains how I did implemented it.

All these data sets are passed to the theme which it renders the 4 items using this Twig template:

{% if more_services %}
    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <div class="border-bottom"></div>
            </div>
        </div>
        <div class="row block-1">
            {% for item in more_services %}
                {% if item.key < 5 %}
                    <div class="col-sm-6 col-md-3 maxheight">
                        <h2><span>{{ item.key }}. </span>{{ item.caption }}</h2>
                        <figure class="img-polaroid"><img src="{{ item.image }}" alt="{{ item.alt }}" title="{{ item.caption }}"></figure>
                        <p>{{ item.description }} <a href="{{ item.href }}" class="link-1"></a></p>
                    </div>
                {% endif %}
            {% endfor %}
        </div>
    </div>
{% endif %}

Only the first 4 items are printed because a Bootstrap row can contains maximum 4 elements with class col-md-3. But Im going to extend this Module so that it will be able to print any numbers of elements which will be set inside the Block Configuration. You can download the entire current implementation and put your hand on my source code from the project Github repository which is downloadable from the article mentioned at the beginning, if you have any questions please get in touch.

1. Open Source

lock icon whilst man is using mouse

Scalable, efficient, highly performing and SEO friendly Dupal CMS. Our Government Intranet project has got more than 50,000 registered users.

2. Support

teamwork brainstorming meeting and new startup project in workplace

Groups organise, meet, and work on projects based on interest or geographic location. It\'s a great way to get involved, learn more and get support.

3. Strategy

creative web designer developing template layout

Do you need a very simple marketing brochure website or a very big robust intranet system? Plan which modules you need to install and keep it simple!

4. Example

reviewing financial reports in returning on investment analysis

Plan your strategy to use Drupal at the best of its capability and performance. As Drupal is written in PHP it requires that a module is..