fromJune 2014
Feature:

Gettin’ Twiggy With It

A Happy Themer?
0

Stack of presents
The web has changed a lot since Drupal 7 was released in 2011.

We went from a simple world where a webpage was 960px wide (and 12 grids) to a complex new responsive world, with gazillions of different screen sizes and resolutions, all controlled by the same markup.

We have seen the introduction of HTML5 and CSS animations, and been witness to the much-anticipated death of IE 6 (and its ill-bred cousin IE 7). No more “IE 6 tax”!

However, all the changes weren’t happy ones; many problems made it difficult for Angry Themers to get their job done. In March 2012 the Drupal frontend community finally got fed up: fed up with database injections (and hacks to get the theme working) and fed up with booting up IDE programs (required to help unravel something-or-other so you can figure out what the heck you’re looking at).

But throwing tantrums in the issue queue got us nowhere.

It was time for the frontend community to band together and demand the change we wanted, which wasn’t Drupal’s “one markup to rule them all, three divs, and four classes” approach. It was time for the frontend developers and the backend developers to start talking.

“Nobody told us what to do.”
- webchick

At Frontend United in Amsterdam, and the simultaneous codesprint in San Francisco, the PHP template system Twig made its appearance and showed us that we could have the best of both worlds; a secure theme layer that was simple AND logical… and the death of the Arrays-of-Doom™.

Old and Busted™ <?php print $foo; ?>
The New Hotness™ {{ foo }}

Our mission is to make the Frontend experience a happy one. The goals are simple:

  • Start with Nothing, add markup & classes as needed
  • Build from usecases – think of the 90%, not the “what-if”
  • Provide tools & visibility
  • Don't dumb it down, complex situations might require a complex template

See http://wdog.it/4/2/principles.

Goodbye Theme Function, Hello Template File!

In Drupal 8, all theme functions have been transformed to template files. Instead of parsing the data through a PHP function buried inside a module, the data is sent to a Twig template file, which lives inside the “templates” folder. This new setup makes it both easier to find, read, and manipulate the output.

Twig 101

In a similar way to how Sass compiles .scss files into CSS, Twig is a compiled templating system that compiles Twig templates down to PHP code.

You can control everything from the template, and best of all, you might never have to look at PHP again. (OK, this may be a slight exaggeration!)

In Twig we have three basic elements; variables, comments, and functions.

Variables

Variables are referenced by surrounding them in double curly braces: {{ your_variable_here }}.

Twig variables are drillable, which means you can walk into an array and easily get the value, without having to know whether it is an object or an array.

Each step is separated by a period (‘.’):

{{ this.is.the.path.to.the.value }}

That is so much simpler than in PHP templates, where it sometimes feels like this:

<?php print $foo->bar['#baz']->where->is->my[0]['und']->value

In Twig, variables are manipulated with a pipe, (|). For example, {{ foobar|upper }} would output: FOOBAR.

Comments

Twig comments are declared with a curly brace and a pound sign, like: {# comment #}.

Functions

Twig functions and conditional logic are referenced with {% %} syntax.

For example, you can create a simple if/else statement with the following:

{% if user.name == mortendk %}
            {{ crown }}
{% else %}
            {{ cake }}
{% endif %}

The set tag can be used to assign values to variables. The following example shows how you can set a variable and then later print out its value:

{% set color %}
blue 
{% endset %}
 
{{ color }} {# prints blue #}

And if that wasn’t enough, we can also say good-bye to the t() function. There’s a new Drupal translation function available in the template files:

{% trans %}
NEJ {{ sweden }} I won't drink your hembrändt.
{% endtrans %}

Translation: “NO, Sweden; I won’t drink your moonshine.”

How This All Works in Drupal 8

Theme in the Themes Folder

The single most confusing thing for new Drupalists has changed. Themes now live where they belong – in the themes/ folder.

Themes installed in themes/ work the same way as sites/all/themes/ did in the past; they are made available for all sites on the installation.

[themename].theme

What we used to know as the template.php file is now the [themename].theme file. If there is a need for additional theme hook suggestions or preprocess manipulations in your theme, it belongs here.

[themename].info

The info file is now written as a YAML file instead of the somewhat made-up .info format that was used previously. For example:

name: Yggdrasil
type: theme
description: twig + viking love = Yggdrasil
package: Core
core: 8.x

Stylesheets

CSS files are registered in the theme’s .info.yml file. As a new feature, you don’t have to do a FOAD on a CSS file (See my column, Removing Module Provided CSS Files in DW issue 3.01). Instead you can ask Drupal politely about removing a CSS file by using stylesheet-remove:

stylesheets:
  all:
    - css/layout.css
  print:
    - css/print.css
 
  stylesheets-remove:
    - system.theme.css
    - user.icons.css

The CSS files name has changed a bit since the BAT (.base.css, .admin.css, theme.css) files that ruled Drupal 7: The file naming principles follow a SMACSS approach.
CSS has changed to a more Object Oriented approach.
Drupal 8 is built on HTML5 and does not support IE 6, 7, or 8. The helper CSS classes that Drupal has carried around for years are no longer needed. Good-bye .odd, .even, .first and .last. Our new friend, CSS3, will take it from here.

div:nth-child(even){... }
div:nth-child(odd){... }
div:first-child{... }
div:last-child{... }

Regions

Regions defined in the theme’s info.yml file are available in the page.html.twig file:

regions:
  header: Header
  menu: Top Menu
  messages: Messages
  content: Content
  footer: Footer

The hard-coded menu, logo, and site name are now removed from page.html.twig and are instead added as a block, which I’m pretty sure has been around since Dries was sitting in his dorm room wearing a sombrero.

Templates

The main page template is page.html.twig. Regions within a page are defined in a similar way as they were in Drupal 7; by adding them to the themename.info.yml file. To get the region to display on a page, we drill into the {{ page }} value. For example, to reference the header you could do something like this:

page.html.twig

{% if page.header %}
  
  {{ page.header }}
  
{% endif %}

Bonus: All IDs in page.html.twig have been removed as well. And there was much rejoicing.

Markup Fun

So far, Drupal 8 doesn't appear to be so different, until you look deeper into a themer’s life. Then it starts to get interesting.
In the dark days of Drupal theming, it would be less painful to get your wisdom teeth removed without sedatives than to walk through Drupal’s inner logic and find and manipulate the Arrays-of-Doom™. Possible, yes, if you had the time and desire to battle Drupal forever.
In Drupal 8, you can make these changes quickly and easily.
For this example, we’re going to use the markup provided below, and we’re going to make it work in a Drupal theme.

<article>
  <footer>
   <div class="tags">
     [n] tag/s:
     <a href="">Odin</i>Thor</>,
     <a href="">Freya</>. 
     </div>
   </footer>
</article>

We start by editing node.html.twig with the following:


...

 {{ content.field_tags}}

..
 {{ content|without('field_tags') }}

In the node template we separate the terms (found in {{content.field_tags}}) away from content and instead we place them in the <footer> tag. In order not to destroy future development, we tell {{ content }} that we have already used field_tags and that it should be printed without the field_tags by using the without() function (specifically, {{ content|without('field_tags') }}).

By default, Drupal uses field.html.twig as a template for all fields, and the default template won't cut it for our needs of total markup control. We can override this by providing field-specific Twig template files. Say good-bye to three <div> wrapping nine classes and countless headaches.

By looking at the debug information (more about debugging later) we know that we can theme this specific template by overriding field--field-tags.html.twig. We can create that file by copying core/modules/system/templates/field.html.twig. We then customize it as follows:

{% for delta, item in items %}

  {# first loop count term/s #}
  {% if loop.first %}
    {% if loop.length > 1 %}
      {{ loop.length }}  tags:
    {% else %} tag:
    {% endif %}
     {{ item }},

  {# 2nd loop add .green + i.icon #}
  {% elseif loop.index == 2 %}
    
      {{ item }},
 
  {# last needs a . #}
  {% elseif loop.last %}
     3 {{ item }}.
 
  {# default #}
  {% else %}
     {{ item }},
  {% endif %}
 
{% endfor %}

The first thing we do in the field template is to loop through all the data, so we can then manipulate it to our heart’s content:

{% for delta, item in items %}
....
{% endfor %}

Then we check to see if this is the first time we have done the loop, using: {% if loop.first %}. Similarly, we check for the second iteration through the loop using {% elseif loop.index == 2 %}. Finally, we check for the last iteration through the loop using: {% if loop.last %}.

In general, {{ loop.index }} is used to reference the count of the loop – starting from 1.
If it’s the first loop, then we test for the loop length using {% if loop.length > 1 %}, so we can figure out if we want a plural or singular term/terms.

Yes, we just did simple frontend logic inside a template. No reason to hide this logic away in a preprocess somewhere that we can't find in four weeks.

In the last loop, the term ends with "." instead of ",".

The last thing we test, if this is the second loop, we add in the green class and <i class="icon></i> inside the <a>.

This is clean markup. Simple, easy, and awesome – no PHP skills or secret Drupal knowledge required.

Twigblock and Extending Templates

Twig blocks, in Twig, are just called a block. In order to not confuse them with the existing blocks inside Drupal, we’ll refer to them as a Twigblock.

The Twigblock can replace part of the original template, if the right situation occurs.
Let’s say we want to replace a part of page.html.twig, when displaying the front page of our site. That can be done with the following code in page.html.twig and page—front.html.twig:

page.html.twig

{% block foobar %}
  im a normal page
{% endblock %}

page--front.html.twig

{% extends "themes/yggdrasil/templates/page.html.twig" %}
{% block headerblock %}
  im the frontpage
{% endblock %}

Template Debugging Tool Built In

One of the biggest issues in Drupal theming is figuring out where specific markup comes from, which files created it, what it’s called, and how to overwrite it.
The solution is to turn on Twig debugging in settings.php by adding:

$settings['twig_debug'] = TRUE;

.

Now when you clear cache and reload your site, take a look on the source of the site (view-source or web inspector/firebug). You can now see all the information needed to find the template files, override them, and manipulate them to your heart’s content. You’ll see something like the following:

<!-- THEME DEBUG -->
<!-- CALL: _theme('node') -->
<!-- FILE NAME SUGGESTIONS:
   * node--1.html.twig
   * node--article.html.twig
   x node.html.twig
-->
<article>
....

Debug Variables

If you want to look into a variable, you can use the dump() function {{ dump( NorseGods ) }}, which works the same way as <?php print_r($NorseGods); ?> in Drupal 7.
If you don't want to bring your server crashing down, be sure to walk through the data available:

{% for key, value in _context  %}
  {{ key }}
{% endfor %}

There you have it, ready to roll with Twig.

I would be lying if I said that Twig isn’t the most exciting thing about Drupal 8. For me, this is a giant leap forward for anyone working with Drupal. We’ve removed the developer-oriented approach to Drupal theming and given that power over to those who actually work with the markup each day.

Resources

Image: ©iStockphoto.com/dra_schwartz