Four Kitchens
Insights

Building with Emulsify part 3: Component complexity

7 Min. ReadDesign and UX

In Part 1, we set up our style guide and got our foundational styling in place. In Part 2, we walked through building a simple Callout component with a variation. In this article, we’re going to focus on a couple of common complexities you will likely run into in Drupal component-driven development — specifically, passing component data between entities and Twig cache tags. Let’s use the common card component for this example. Below are some example designs:

Emulsify Card title

Architecture

For the purposes of this article, we are going to take the approach in Drupal of building a Card paragraph that has an Entity Reference field to reference nodes. The node itself is actually what will supply the title, body, and the link you see in the above examples. And we will also have a Variation field on the paragraph to select between the two variations (Default or “Stripe”). We  covered how to create variations like this in Part 2, so this article merely focuses on the complexity specific to this Entity Reference approach.

Passing data between entities

So, we want the administrator to be able to add a Card paragraph referencing a node, but we also want them to select what card Variation they want at the paragraph level. For the variation, let’s add a field named “Variation” of type “List (text)” to our Card paragraph. In the Allowed Values, let’s add stripe|Stripe (leave the field unrequired so the default variation will display if nothing is selected). The Variation select list is now available within our card paragraph, which brings us to our first point of complexity.

Card component and node template

In our last article, we covered a component that is directly mapped to a single level (Callout component mapped to Callout paragraph template in Drupal). In this case, it is actually the referenced node fields (node title, body, and link) that we want to map to our Card component, so the ideal place to map that is obviously in the node template, not our paragraph template.

This brings us to a rule (or at least strong recommendation) of component-based theming. Choose the Drupal template that maps most closely to your component variables. Our paragraph may be named “Card,” but it does not directly have the fields we need to map to the card component (card title, card body, and card link)—those fields are provided by the node itself. If we used the paragraph template to map our card component, we will have to dig through the paragraph array into the node array to then get to our field values. The more you try to traverse Twig arrays in Drupal templates, the more difficult and unwieldy your Twig code gets and the more likely you are to use unsafe/unreliable methods to retrieve the field values. Twig is not suited for this kind of array traversal. So, when at all possible, make your life easier by using the most directly applicable template.

To theme the node, let’s make sure our display is correct. First, let’s add a new Display View mode (content) called “Card.” That way we can use it on whatever content type we like, and Drupal will automatically give us a template override, as we’ll see in a minute.

Emulsify - Manage display setting

Once we’ve done that, we can go to “Manage Display” for the content types we want to reference in our paragraph and under “Custom Display Settings,” add “Card,” and save.

Now, we will see a new tab for the Card display on the Manage display page of the content types where we added that. Click on that Card tab and let’s select the fields we would like to map in our Card. For our example above, we actually just need the body field because title and the node url (the two fields needed for the card link and card title fields in our component) are available by default in the node template. 

Emulsify - Format body field

Once we’ve saved our settings there, go to the Card paragraph, manage the display of it and for the entity reference field, select “Rendered Entity” as the format and “Card” as the display mode like below:

Emulsify - Rendered entity

Once we’ve added the Card paragraph to a content type and created a piece of content with that paragraph populated (and have Twig debugging enabled), we should now see the new template options on the frontend.

Emulsify - Node card

Because of our new Card display mode, we can use node--card.html.twig for our template and this will be used for any content type shown using the Card display. Inside this new template, let’s replace the default Twig/markup with the following:

{% include "@molecules/card/card.twig" with {
  card_title: label,
  card_body: content.body,
  card_link_url: url,
  card_link_text: "View Content",
} %}

This should be familiar from Part 2, and we now have our card component mapped correctly. 

You can see above that we have mapped all the fields we need, except for the Variation field. And here’s the crux of this paint point: That field is a paragraph field, which is not available in our node template. So, we need to do a bit of PHP coding to pass that value from the paragraph into this node template. Open up the theme’s .theme file, and let’s put the following code in there (replacing THEMENAME with your theme’s name):

/**
 * Implements theme_preprocess_node.
 *
 * @param array $vars
 *   Array of variables containing field content.
 */
function THEMENAME_preprocess_node(array &$vars) {
  // If a card.
  if ($vars['view_mode'] === 'card') {
    // Get parent entity (card paragraph).
    $fieldParagraph = $vars['node']->_referringItem;
    $fieldReference = $fieldParagraph->getParent();
    $fieldReferenceEntity = $fieldReference->getEntity();
    // Get card variation.
    $fieldVariation = $fieldReferenceEntity->get('field_variation');
    if ($fieldVariation[0]) {
      $vars['card_variation'] = strtolower($fieldVariation[0]->getString());
    }
  }
}

This code preprocesses a node and if it is using the “Card” display mode, it then gets the parent referring entity (in our case, the Card paragraph). It then gets the Variation field and if it is populated, passes the value of that field in as a new variable available in our node template called card_variation. Now that the node template has our variation field value, we can add the following code at the top of node--card.html.twig:

{%
  set card_modifiers = [
    card_variation == 'stripe' ? 'stripe' : '',
  ]
%}

Assuming you are familiarized with the BEM function from Part 2, you’ll know that card_modifiers is a variable that exists in the Card component. It accepts an array that will then be appended to the base_class in the BEM function (e.g., if the base_class is card, and you pass [‘stripe’] to card_modifiers, your element classes would appear as <div class=”card card--stripe”>. In our example, if our new card_variation variable matches the string ‘stripe’, then it will add that same string as the modifier. (You may have also noticed that you could use something like card_variation ? card_variation : '' if your Variations select list grows and you know create machine_names that match your classes!).

So, now we have successfully added the Variable select list to our Card paragraph and have mapped the result to our node template. This works great for the most part, but there is one catch. If we edit the paragraph and change our variation, there is a possibility that the change won’t appear immediately on the frontend depending on your level of caching in place. This leaves us with our last point of complexity: Twig caching.

Twig caching

The sophisticated layers of caching in Drupal are one of its greatest strengths—this is just one scenario where we need to do some extra work to get the behavior we expect. The problem here is that when you edit your card and change the variation, Drupal is smart enough to clear the paragraph cache but has no knowledge of the node correlation we made in code in the previous step. We need to write a little more PHP to force it to also invalidate the Twig cache for the node. Open up a custom module .module file (create a custom module if you have to) and add the following code (changing MODULENAME to your module name):

use DrupalCoreCacheCache;

/**
 * Implements hook_ENTITY_TYPE_update().
 */
function MODULENAME_paragraph_update($entity) {
  if ($entity->hasField('field_variation') && $entity->get('field_variation')->getValue() != $entity->original->get('field_variation')->getValue()) {
    foreach ($entity->get('field_content_card')->referencedEntities() as $card) {
      $tags = $card->getCacheTagsToInvalidate();
      Cache::invalidateTags($tags);
    }
  }
}

In this snippet, when a paragraph gets updated and it has the Variation field and the Variation field has been changed, we loop through that paragraph’s Entity Reference field (field_content_card) and “invalidate” the cache tags for those entities as well.

Conclusion

So, we have covered getting ramped up, setting a basic component and a common point of component complexity. This leaves us with our last step of mapping multiple components in rows or a grid (what in Atomic Design we would often call an organism). We’ll cover this in our last article.

Read more of the Building with Emulsify series: