4

I have a view which displays all pictures of the content type photo. I've set some fields in this view for the display.

This is how iIrender the list of pictures:

views-view-fields--photo.html

<figure class="element-item {{ fields['name'].content|lower }}">
    <a href="{{ fields['field_photo_1'].content|lower }}">
        <img src="{{ fields['field_photo'].content }}"/>
    </a>
    <figcaption>
        <div class="date">{{ fields['field_date'].content }}</div>
        <div class="lieu">{{ fields['field_lieu'].content }}</div>
    </figcaption>
</figure>
{{ drupal_block('comment_block') }}

I use the Twig tweak module to render a custom block in this template. This block should load all comments for the picture. For that, I need to pass the nid of the node to my block.

Here's the code of the block:

CommentBlock.php

/**
 * Provides a 'CommentBlock' block.
 *
 * @Block(
 *  id = "comment_block",
 *  admin_label = @Translation("Comment block"),
 *  context = {
 *    "node" = @ContextDefinition("entity:node", label = @Translation("Node"))
 *  }
 * )
 */
class CommentBlock extends BlockBase implements ContainerFactoryPluginInterface {

  /**
   * Drupal\Core\Entity\EntityTypeManagerInterface definition.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Constructs a new CommentBlock object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param string $plugin_definition
   *   The plugin implementation definition.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager')
    );
  }

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

    $build = [];

    $node = $this->getContextValue('node');
    kint($node->id());

    $build['#theme'] = 'comment_ajax';

    return $build;
  }

}

What I try is to get the context of the views row into my block. But I get this error:

Drupal\Component\Plugin\Exception\ContextException : Required contexts without a value: node

What am I doing wrong?

I can load my block from template_preprocess_views_view_fields() to link with the nid but I try to understand the context annotation.


Update

Here's a picture of the view, my custom block returns just a link name "Comment".

enter image description here

I now load the block programmatically instead of using drupal_block in the preprocess.

$block             = \Drupal\block\Entity\Block::load('commentblock');
$variables['test'] = \Drupal::entityTypeManager()
  ->getViewBuilder('block')
  ->view($block);

Then I print this block in views-view-fields.html.twig:

{{ fields['title'].content }}
{{ test }}

If I kint the context in my block I still get an error except if I set the annotation required = FALSE.

 *   context = {
 *     "node" = @ContextDefinition(
 *       "entity:node",
 *       label = @Translation("Current Node"),
 *       required = FALSE,
 *     )
 *   }

But the context is still empty.

3
  • I'd really love to know how you managed it in the end and are looking forward to see a cool answer from you added below. You'll find a way. I'm sure!
    – leymannx
    May 25 '18 at 8:29
  • 1
    as soon as i find a way i ll update this post, thanks :)
    – Kevin
    May 25 '18 at 8:43
  • Just a note if you arrive here from Google: As of Drupal v9 you have to use the context_definitions annotation context_definitions = {"node" = @ContextDefinition(...)} instead of the old context = {"node" = @ContextDefinition(...)}
    – Hudri
    Aug 16 at 16:01
4

When placing the block you have to choose from where the block will get the node value.

Select a node value

enter image description here

Node from URL

enter image description here


Ah! I found my own issue on DO. You also might have to unrequire the context like following:

 *   context = {
 *     "node" = @ContextDefinition(
 *       "entity:node",
 *       label = @Translation("Current Node"),
 *       required = FALSE,
 *     )
 *   }
7
  • The block is placed programmatically, and i have like 50 picture render by the views module on one url
    – Kevin
    May 24 '18 at 21:43
  • @Kevin – Answer updated.
    – leymannx
    May 24 '18 at 22:20
  • @Kevin – But I'm afraid this only kills the error but still won't give you what you want. Have you already seen this answer? Under "Plugin blocks" he seems to mention where you can hook in to add contexts.
    – leymannx
    May 24 '18 at 22:27
  • 2
    @Kevin – And I guess drupal_block() inside Twig is too limited. I'd say preprocess function and pass a render array over to Twig instead.
    – leymannx
    May 24 '18 at 22:32
  • 1
    I was initially confused why my block did not have a select field for the node context - it is required to set validation criteria on the view's contextual filter first for the field to appear.
    – gapple
    Feb 21 '19 at 3:57
2

So after many try, I haven't found a standalone solution for my block with the context plugin. But I found a way with a preprocess function.

Here's how I get the nid from my views row :

function comment_ajax_preprocess_views_view_fields(&$variables) {

  $block_manager = \Drupal::service('plugin.manager.block');
  $config        = ['nid' => $variables['row']->nid];
  $plugin_block  = $block_manager->createInstance('comment_block', $config);

  $variables['blockComment'] = $plugin_block->build();
}

In this preprocess function, I get the row nid and I use it in the block config.

And in my custom block:

public function build() {

  $build = [];

  $blockConfig     = $this->getConfiguration();
  $build['#theme'] = 'comment_ajax';
  $build['#nid']   = $blockConfig['nid'];

  return $build;
}

I get back the nid from the block configuration.

That works but I was looking for a way without preprocessing, just with the context.

I'm still looking for a better way :)

2

Block-plugins that inherit from BlockBase can have their context values directly set via ContextAwarePluginBase::setContextValue($key, $value). This method can be used by a preprocess function to inject context that isn't otherwise available from the route or environment.

In my case I have a block that needs a group entity in its context:

<?php

namespace Drupal\middlebury_course_hub\Plugin\Block;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Session\AccountInterface;

/**
 * Provides a block with a link to the section's roster.
 *
 * @Block(
 *   id = "course_hub_roster_link",
 *   admin_label = @Translation("Roster Link"),
 *   category = @Translation("Course Hub"),
 *   context = {
 *     "group" = @ContextDefinition(
 *       "entity:group",
 *       label = @Translation("Current Group"),
 *       required = FALSE
 *     )
 *   }
 * )
 */
class RosterLinkBlock extends BlockBase {

  /**
   * {@inheritdoc}
   */
  protected function blockAccess(AccountInterface $account) {
    /** @var \Drupal\group\Entity\GroupInterface $group */
    if (($group = $this->getContextValue('group')) && $group->id()) {
      if ($group->hasPermission('view group_membership content', $account)) {
        return AccessResult::allowed();
      }
    }
    return AccessResult::forbidden();
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    // This block varies per group.
    $build['#cache']['contexts'] = ['group'];
    $build['#theme'] = 'course_hub_roster_link';
    /** @var \Drupal\group\Entity\GroupInterface $group */
    if (($group = $this->getContextValue('group')) && $group->id()) {
      $build['#group'] = $group;
    }

    // If no group was found, cache the empty result on the route.
    return $build;
  }

}

I wanted to display this block for each group entity that was a row of a view result. By adding a preprocess function to my module I could inject the group context:

/**
 * Implements hook_preprocess_hook().
 */
function middlebury_course_hub_preprocess_group(&$variables) {
  if ($variables['view_mode'] == 'dashboard_item') {
    // Add the roster-link block to the build-array.
    $block = \Drupal::service('plugin.manager.block')
               ->createInstance('course_hub_roster_link');
    $block->setContextValue('group', $variables['group']);
    if ($block->access(\Drupal::currentUser())) {
      $variables['content']['roster_link'] = $block->build();
      $variables['content']['roster_link']['#weight'] = 2;
    }
  }
}

This same technique should work for nodes as well using $block->setContextValue('node', $node); in your hook_preprocess_node function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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