Drupal 8: Theming

Theming basics & TWIG for Drupal 8 modules
Drupal Meetup Stuttgart

07/02/2015

 

1. Introduction:
Template Engines

Once upon a time, in a D7 module...

function mymodule_block_content() {

  $output = '';

  $users = mymodule_get_some_users();

  $output = '<ul class="list" style="margin-top:10px">';
  foreach ($users as $user) {
    $output .= '<li class="item"><em>' . $user->name . '</em></li>';
  }
  $output .= '</ul>';

  return $output

}
  • Markup and styling mixed with business logic
  • Hardly to be overwritten

We can do this a little bit better:

function mymodule_block_content() {
  $users = mymodule_get_some_users();
  $output = theme('mymodule_block', $users);
  return $output
}

function mymodule_theme() {
  return array(
    'mymodule_block' => array(
      'variables' => array('users' => NULL),
    ),
  );
}

function theme_mymodule_block($variables) {
  $users = $variables['users'];
  $output = '<ul class="list" style="margin-top:10px">';
  foreach ($users as $user) {
    $output .= '<li class="item"><em>' . $user->name . '</em></li>';
  }
  $output .= '</ul>';
  return $output
}
  • Can be overwritten, in template.php (mytheme_mymodule_block)

We can do this much more better:

function mymodule_block_content() {
  $users = mymodule_get_some_users();
  $output = theme('mymodule_block', $users);
  return $output
}

function mymodule_theme() {
  return array(
    'mymodule_block' => array(
      'variables' => array('users' => NULL),
      'template' => 'mymodule_block'
    ),
  );
}

and in mymodule_block.tpl.php:

<ul class="list" style="margin-top:10px">
<?php foreach ($users as $user) : ?>
  <li class="item"><em><?php print $user->name; ?></em></li>
<?php endforeach; ?>
</ul>
  • Template file can be copied and overwritten
  • And maybe use CSS, not markup or inline styles...

Voilá, a Template Engine!

(aka Theme Engine)

Drupal 4.7 / 5 / 6 / 7: PHPTemplate

Drupal 8: TWIG

Variables

What's wrong with PHPTemplate?

  • Drupal proprietary stuff
  • Requires PHP knowledge
  • Hard to read
  • Potentially insecure, e.g. printing malicous strings
  • Empowers business logic, e.g. DB queries
  • One typo can take your site offline
  • Sooo old-style

So Drupal 8 goes

PHPTemplate Smarty PHPTAL TWIG

2. Building a Drupal 8 Theme

Create a theme folder structure

- config
  - schema
    - mytheme.schema
- css
  - base.css
  - layout.css
- images
- js
  - special.js
- libraries
  - flexslider
- templates
  - node.html.twig
  - page.html.twig
logo.svg
screenshot.png
mytheme.breakpoints.yml
mytheme.info.yml
mytheme.libraries.yml
mytheme.theme

/themes/custom/mytheme/

Tell Drupal about your theme

name: 'My theme'
type: theme
description: 'Just a sample theme for Drupal 8.'
package: Custom
core: 8.x
libraries:
  - mytheme/global
stylesheets-remove:
  - core/assets/vendor/normalize-css/normalize.css
regions:
  header: Header
  content: Content
  sidebar_first: 'Sidebar first'
  footer: Footer

/themes/custom/mytheme/mytheme.info.yml

Tell Drupal about css/js libraries in your theme

# This one is specified in mytheme.info.yml and loaded on all pages
global:
  version: VERSION
  css:
    theme:
      css/base.css {}
      css/layout.css: {}

# Those are needed in some special places and loaded via #attached['library']
special:
  version: VERSION
  css:
    theme:
      css/special.css: {}
  js:
    js/special.js: {}
flexslider:
  version: '2.5.0'
  css:
    theme:
      libraries/flexslider/flexslider.css: {}
  js:
    libraries/flexslider/jquery.flexslider.js: {}
  dependencies:
    - core/jquery

/themes/custom/mytheme/mytheme.libraries.yml

Add preprocessing stuff, if needed

<?php

/**
 * Implements hook_element_info_alter().
 */
function mytheme_element_info_alter(&$type) {
  // We require Modernizr for button styling.
  if (isset($type['button'])) {
    $type['button']['#attached']['library'][] = 'core/modernizr';
  }
}

/themes/custom/mytheme/mytheme.theme

Formerly known as template.php!

Specify breakpoints, if needed

mytheme.mobile:
  label: mobile
  mediaQuery: ''
  weight: 2
  multipliers:
    - 1x
mytheme.narrow:
  label: narrow
  mediaQuery: 'all and (min-width: 560px) and (max-width: 850px)'
  weight: 1
  multipliers:
    - 1x
mytheme.wide:
  label: wide
  mediaQuery: 'all and (min-width: 851px)'
  weight: 0
  multipliers:
    - 1x

/themes/custom/mytheme/mytheme.breakpoints.yml

Add the remaining stuff...

  • Templates
  • Scripts
  • Stylesheets
  • Images
  • Logo
  • Screenshot
  • ...

3. Theming basics for modules

Good News!

Not so many changes

Main difference: no more theme() functions!

Registering templates

(no more theme functions!)

function mymodule_theme($existing, $type, $theme, $path) {
  return array(
    'mymodule_something' => array(
      'variables' => array('something' => NULL, 'otherthing' => NULL),
    ),
  );
}

hook_theme(), in your module file

Create a template

(*.html.twig, not *.tpl.php)

{% if something %}
  <p>
    {{ something }}
    {{ otherthing }}
  </p>
{% endif %}

mymodule-something.html.twig, in your module's template folder (/modules/custom/mymodule/templates)

Use this somewhere

(using render array, not theme())

...

$output = array(
  '#theme' => 'mymodule_something',
  '#something' => 'Something',
  '#otherthing' => 'Otherthing',
);

...

Somewhere in your code (controller, plugin, ...)

Alter some stuff

(mainly provided by other modules)

/**
 * Implements hook_preprocess_HOOK().
 */
function yourmodule_preprocess_mymodule_something(&$variables) {

  ...

}

In your module file or theme

4. TWIG Basics:
Syntax

Print / Output:

{{ some content }}

 

Comments:

{# this is a comment #}

 

Execute statements:

{% expression %}

5. TWIG Basics:
Variables

Accessing variables

Simple variables (strings, numbers, booleans):
{{ foo }}

Complex variables (arrays / objects):

Objects & arrays:
{{ foo.bar }}
Alternative:
{{ attribute(foo, 'bar') }}

Only arrays:
{{ foo['bar'] }}

Example from views module:

{% if attributes -%}
  <div{{ attributes }}>
{% endif %}
  {% if title %}
    <h3>{{ title }}</h3>
  {% endif %}
  <{{ list.type }}{{ list.attributes }}>
    {% for row in rows %}
      <li{{ row.attributes }}>{{ row.content }}</li>
    {% endfor %}
  </{{ list.type }}>
{% if attributes -%}
  </div>
{% endif %}

Assigning values to variables

{% set foo = 'bar' %}

{% set foo, bar = 'foo', 'bar' %}

{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}
{% set foo = 'foo' ~ 'bar' %}

{% set foo %}
    <div id="pagination">
        ...
    </div>
{% endset %}

More examples:

http://twig.sensiolabs.org/doc/tags/set.html

6. TWIG Basics:
Control structures

{% if ordered_list %}
  <ol>
{% else %}
  <ul>
{% endif %}

IF ... THEN ... ELSE ... ENDIF

{% for item in items %}
  <li>{{ item }}</li>
{% endfor %}

FOR ... ENDFOR

{% include '@mytheme/parts/footer.html.twig' %}

INCLUDE -> fetch another template file

{% block footer %}
  © Copyright 2015 drubb
{% endblock %}

BLOCK -> section that can be repeated, or  overwritten by other template files

7. TWIG Basics:
Filters & Functions

{{ title|upper|trim('.') }}

{% filter upper %}
  This text becomes uppercase
{% endfilter %}

More examples: abs, first, last, length, lower, reverse, round,...)

http://twig.sensiolabs.org/doc/filters/index.html

General Twig filters

Drupal specific filters

Translate:
<a href="{{ url('<front>') }}" title="{{ 'Home'|t }}" rel="home" class="site-logo"></a>

Without:
{{ content|without('links') }}

Safe join:
{{ items|safe_join(", ") }}

More examples:
https://www.drupal.org/node/2357633

Filter: simple transformations on output

{{ max(1, 3, 2) }}

{{ random(['apple', 'orange', 'citrus']) }}

More examples:

http://twig.sensiolabs.org/doc/functions/index.html

General Twig functions

Drupal specific functions

Url:
<a href="{{ url('<front>') }}" title="{{ 'Home'|t }}" rel="home" class="site-logo"></a>

Path:
{{  path('entity.node.canonical', {'node': node->id()}) }}

More examples:

https://www.drupal.org/node/2486991

Function: takes arguments to compute output

8. TWIG Basics:
Inheritance

<header>
    ...
</header>

<article>
    ...
</article>

<footer>
    {% block footer %}
        <h4>This is the footer coming from Template 1!</h4>
    {% endblock%}
</footer>

Second template (special.html.twig)

First template (default.html.twig)

{% extends "default.html.twig" %}

{% block footer %}
    {{ parent() }}
    <h5>But there's a second line now!</h5>
{% endblock%}

9. TWIG Basics:
Example

{#
/**
 * @file
 * Theme override to display a block.
 *
 * Available variables:
 * - ...
 *
 * @see template_preprocess_block()
 */
#}
{%
  set classes = [
    'block',
    'block-' ~ configuration.provider|clean_class,
  ]
%}
<div{{ attributes.addClass(classes) }}>
  {{ title_prefix }}
  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}
  {{ title_suffix }}
  {% block content %}
    {{ content }}
  {% endblock %}
</div>

block.html.twig

Thank You!

 

http://slides.com/drubb

http://slideshare.net/drubb

Drupal 8: Theming

By Boris Böhne

Drupal 8: Theming

Theming basics & TWIG for Drupal 8 modules, a presentation at Drupal Meetup Stuttgart, 07/02/2015

  • 1,306