Twig for Drupal 8 Development: Twig Templating Part 2 of 2
In the recent post Twig for Drupal 8 Development: Twig Templating Part 1, we covered some Drupal Twig templating basics like debugging, custom templates, inheritance, variables, filters, attributes, and macros. This post will cover more advanced topics. You will learn about preprocessing variables, expanding the available templates with theme suggestion, Drupal 8 Views and Twig, and the Twig Tweak module.
In Drupal 8, preprocesses are powerful tools that are defined in the .theme file of your theme. You can preprocess pretty much any entity—nodes, blocks, fields, terms, forms, paragraphs, even the HTML structure itself to create the variables that will cross from one entity type to another.
Without the help of preprocessing, you couldn’t add things like a node title to a block because node variables are not available to blocks. Creating something like a custom breadcrumb block for a content type would not be possible. Using the include method discussed in Part 1 also wouldn't work because the final output we want is a block, so only existing block variables are available for replacement. Thankfully, you can preprocess the block and use Drupal's \Drupal::routeMatch()->getParameter('node'); functionality to find the node the block lives on. Then you can use a ->getTitle(); method to find the title and assign it to a variable $variables['nodetitle'] = $nodetitle;. Since we are preprocessing a block, the {{ nodetitle }} variable is now available for use in any block Twig template.
You can now create a custom block of type basic block, add it to a content type and create a custom block--basic.html.twig template for the breadcrumbs. In the example below we are using Drupal's canonical path {{ path('entity.node.canonical', {'node': '1')}) }} to get the url alias for the Resource Library part of the breadcrumb trail. You could replace the '1' with a node id variable (which you'd have to add in the node title preprocess function to gain access to that variable), but since that node isn't going to change, we just used the static node id.
You may remember from the Custom Templates section of Part 1, however, that if we had more than one custom basic block on our site—the name used for this custom template would replace all of the custom block content on the site with these breadcrumbs.
While there may not be a lot of custom blocks on the site, we for sure want a different set of breadcrumbs for each content type and so we will need a custom block for each. In order to get specific enough template names so that we can use a different template for each set of breadcrumbs, we'll have to create a theme suggestion for the blocks on the site.
Theme suggestions expand Drupal 8’s native theme templates to pretty much anything you need. You can expand the basic page.html.twig to allow for content type specific templates by using a mytheme_theme_suggestions_page_alter function. You can get paragraph bundle specific templates or even single paragraph instance specific templates by by changing mytheme_theme_suggestions_page to mytheme_theme_suggestions_paragraph. As with preprocess hooks, any entity can have theme suggestions.
So to get custom templates for each of the breadcrumb blocks, first create the blocks in the Block Layout UI. Just create blocks of the basic block type and assign labels to them. There is no need to give them any content since we're doing that in the custom templates. After the blocks are created, use a block theme suggestion to find the block label and splice it into the suggestions array. For good measure, we're also finding the block type and splicing that in as well as making sure that we use some basic php str_replace(' ','_',strtolower($variables['elements']['content']['#block_content']->label())); to ensure that the block label produces a clean class name.
Now you can use the label of each custom breadcrumbs block as the template name to get unique breadcrumbs for each content type.
Drupal 7 views were about the best thing ever and, thankfully, they’ve been included in Drupal 8 core. Strangely though, we find ourselves using less and less of them in Drupal 8 because of the ease and power of Twig.
We did notice that views are by far the most difficult thing to customize through Twig that we’ve found in D8. It's difficult to bring in content from one views field into another in a views field twig template—although it can be done with Twig in the Views UI rewrite. If we need to customize multiple fields on a view, we usually find ourselves rewriting the views display views-views-unformatted--myview--mydisplay.html.twig template—which can be an overkill if we are only customizing a couple of fields on the view.
We recently found that Views UI rewriting and the global custom text field stripped the audio formatting of a field we needed to group into a custom HTML structure with a couple of other fields. Our only choice was to render the field with the audio formatter and write the HTML needed around it and the other fields in the view display template itself.
We looped through the rows and found the row.content[‘#view’].style_plugin.render_tokens[ loop.index0 ] to get the field output. That gave the custom display template the rendered output of each field in the view—basically creating exactly what the view outputs without the views-field div wrappers. We could then print the rendered fields in the template in any order and with any HTML structure needed and the necessary rendered formatting.
If empty with views custom templates and Twig debugging
In the example above, we have some fields that we know are going to be constantly populated and some that aren’t. Since we’re writing custom HTML around the view fields, we need to use Twig’s {% if %} functionality to make sure that HTML is printed only when there is content to fill it. Normally it is very easy in Twig to write an if statement to include things if there is content to fill them. With views templates however, things we're not quite so easy. We tried hiding the rewriting on the field in Views UI. We tried hide if empty in the Views UI. We tried creating a views field template for that field and using a Twig if statement there to see if a lower level hierarchy or more specificity than just the rendered field in the display template would work.
Finally, we realized that Twig debug adds content to the view. All of that green ← THEME DEBUG → code you see in your browser inspector when debug is enabled counts as content to views. You can beat that by using Twig's |striptags|trim filters on your rendered views field output. For some fields though, you need to preserve some tags and Twig's autoescape function likes to print the tag as content in the views display template.
In the example above, we're printing the rendered body field of the podcast episode which can have HTML added by an editor. We could probably have set up some sort of Twig {{ autoescape false }} in the views field template for the body (by the time its rendered in the display template, its too late) and done it that way, but since debugging should only be used on local development sites, why bother? Turning debugging off in the services.yml file makes the if statements work as intended so we know that they will function properly when they get to production.
Twig Tweak is a Drupal 8 contributed module that expands Twig’s default filters to include things like rendering blocks and other entities, setting images styles, printing menus and a whole lot more. You can find a list of the available functions in the newly released 2.0 version in their cheatsheet.
Render media entities with Twig Tweak
If you’re using a custom media solution like we defined in our recent Drupal 8 Media Libraries post you’ll probably use the drupal_entity function often. That alone makes Twig Tweak worth installing. The drupal_entity function can render an entity using any view mode you specify. To render an image, just let the function know what type of entity it is, the entity id and the view mode you want to use to render {% set image = drupal_entity('media', paragraph.field_oc_image.0.target_id, 'large') %}. Now that Twig Tweak lets us do that in a custom template, we can create a image paragraph bundle with an image media entity reference and a size select list that lets an editor choose their own image style when adding an image to the node.
Join the Discussion +