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
- http://wdog.it/4/2/dt
- Twig documentation: http://wdog.it/4/2/docs
- Yggdrasil http://wdog.it/4/2/doy / http://wdog.it/4/2/ghy
Image: ©iStockphoto.com/dra_schwartz