Add a variable from a paragraphs field to the HTML or body class attribute in Drupal.

Published May 13, 2022
Updated Jul 12, 2022
By Simon

As noted in the first part of this article, using the Drupal static variable function may not be the best design pattern but for now, this works. Also, the Drupal static variable is marked for deprecation so you need to be aware of that. When I work out a better solution I will post it here but regardless of your thoughts, this solution works and is helpful to someone new to programming.

I have written how you can do the same thing using the Drupal routematch service. You can read about that in the Using Drupal::routeMatch() to add node and paragraphs values to the body class using an HTML preprocess function article. However, to understand how to get the value from a paragraph field please read this article.

If you followed along with part 1 you will have a static variable set up so we only need to get the value from the paragraph field. To do that we need to include the Paragraph class in our THEME_NAME.theme file.

Add this to the top of the THEME_NAME.theme file.

use Drupal\paragraphs\Entity\Paragraph;

From the first part of this article, where we added a class created from a node field to the body element, you will already have a node pre-process function like the one below. If not add this now to your .theme file and include the $node variables so we have access to all the node data.

function kodomo_preprocess_node(&$variables)
{
  $node = $variables['node'];
}

We use the node pre-process hook function block because the field is a reference field in our content type as illustrated below.

Image
paragraph reference field on the content type manage fields page in Drupal

Inside our node pre-process function follow the below steps:

  1. Since the field is only in our article content type I am going to use a switch statement on the node or content type. In this case, we use the machine name which is article. You also need to make sure the field has a value by using an is not set statement and a break.

    switch ($node->getType()) {
    
      case 'article':
    
      if (!isset($node->field_paragraph_ref)) { break; }
      if (isset($node->field_paragraph_ref)) {
    
      }
    
    }
  2. Next, we need to create a variable of our reference field so we can drill down into the values in the paragraph entity. This is the powerful nature of Drupal's reference fields.

    if (isset($node->field_paragraph_ref)) {
    
      $paragraphs = $node->field_paragraph_ref;
    
    }
    
  3. Next, we need to get the value of our field from the referenced entity. This was a bit of trial and error on my part and after a bit of research and errors I worked out we need to use the public static function ItemList::first from the class ItemList and we need to extract the target ID.

    if (isset($node->field_paragraph_ref)) {
    
      $paragraphs = $node->field_paragraph_ref;
    
      $paragraphEntity = $paragraphs->first('target_id')->getValue();
    
    }
  4. Once we have the target ID it's all plain sailing. We need to load the paragraph entity, this could be named anything, and pass it to another variable. I called the variable paragraph to indicate it is a singular of the paragraphs module but again you can name this anything you like.

    if (isset($node->field_paragraph_ref)) {
    
      $paragraphs = $node->field_paragraph_ref;
    
      $paragraphEntity = $paragraphs->first('target_id')->getValue();
    
      $paragraph = Paragraph::load($paragraphEntity['target_id']);
    
    }
  5. From here we can then get the field we need and create a variable. Note that I have checked that the field value is set first.

    if (isset($node->field_paragraph_ref)) {
    
      $paragraphs = $node->field_paragraph_ref;
    
      $paragraphEntity = $paragraphs->first('target_id')->getValue();
    
      $paragraph = Paragraph::load($paragraphEntity['target_id']);
    
      if (isset($paragraph->field_number_style->value)) {
              
         $tsValue = 'banner-style-' . $paragraph->field_number_style->value;
         $variables['theme_style'] = _myTheme_var('my_var', $tsValue);
    
      }
    
    }
  6. As shown in the above code we then use the static function _myTheme_var() to set the theme_style variable.

That's it we now have set the static variable to a new value and we can then add it to the body class or use it anywhere in the node for that matter.

Get the variable in the HTML pre-process function and add it to the variable/attributes

This step is no different from using the variable of a content type field in the HTML pre-process hook as discussed in the first part of the article. Here is the code for reference. 

function kodomo_preprocess_html(&$variables)
{
  $variables['theme_style'] = _myTheme_var('my_var');
}

Finally, you need to add the variable to the attribute in the HTML twig template file.

{%
  set body_classes = [
    logged_in ? 'user-logged-in',
    not root_path ? 'path-frontpage' : 'path-' ~ root_path|clean_class,
    node_type ? 'page-node-type-' ~ node_type|clean_class,
    db_offline ? 'db-offline',
    theme_style
  ]
%}

That's it now you have the value from the paragraph field rendered in the HTML.

Now in your CSS, you can use the new banner-style-CUSTOM_VALUE class. You will also need to chain the page-node-type-TYPE_MACHINE_NAME  so that the style is only applied to the pages that need the style. This is because view pages that have a list of all the pages will also add a value to the class attribute. So you a CSS selector rule like below.

.page-node-type-TYPE_MACHINE_NAME.banner-style-CUSTOM_VALUE {
  /* Add you rules in here */
}

 

Note that for this to work as outlined in this article you need to make the field in the paragraph entity the first in the reference field. Also if you allow many paragraph types to be added to the reference field you need to make sure that you keep the style field at the top. For this reason, I suggest that you don't give the flexibility of adding multiple paragraph types for this type of implementation.

I used this recently on a site that used paragraphs to add a special hero banner to the top of the article page. There was an option to add a full hero image instead of the standard header banner. When the image was used the header logo and menu needed to sit on top of the image. This required a class on the body so that the header text and logo could be styled in white when the style was chosen.

This adds a nice unique template option to the site that can be used for feature pieces or even landing pages that can take on a whole new feel from the main site. The options are endless.

I hope you enjoyed this article. If you are keen to learn more about Drupal and front-end development and design, please sign up for the newsletter for a brew of weekly tips and the odd giveaway. Thanks for reading and see you soon.