Drupal 8: ThemingDrupal 8: Theming
Theming basics & TWIG for Drupal 8 modulesTheming basics & TWIG for Drupal 8 modules
Drupal Meetup StuttgartDrupal Meetup Stuttgart
07/02/2015
1. Introduction:1. Introduction:
Template EnginesTemplate Engines
Once upon a time, in a D7 module...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: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: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!Voilá, a Template Engine!
(aka Theme Engine)
Drupal 4.7 / 5 / 6 / 7: PHPTemplate
Drupal 8: TWIG
Variables
What's wrong with PHPTemplate?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 goesSo Drupal 8 goes
PHPTemplatePHPTemplate SmartySmarty PHPTALPHPTAL TWIGTWIG
2. Building a Drupal 82. Building a Drupal 8
ThemeTheme
Create a theme folder structureCreate 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 themeTell 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 themeTell 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 neededAdd 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 neededSpecify 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...Add the remaining stuff...
Templates
Scripts
Stylesheets
Images
Logo
Screenshot
...
3. Theming basics for3. Theming basics for
modulesmodules
Good News!Good News!
Not so many changesNot so many changes
Main difference: no more theme() functions!
Registering templatesRegistering 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 templateCreate 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 somewhereUse this somewhere
(using render array, not theme())
...
$output = array(
'#theme' => 'mymodule_something',
'#something' => 'Something',
'#otherthing' => 'Otherthing',
);
...
Somewhere in your code (controller, plugin, ...)
Alter some stuffAlter 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:4. TWIG Basics:
SyntaxSyntax
Print / Output:
{{ some content }}{{ some content }}
Comments:
{# this is a comment #}{# this is a comment #}
Execute statements:
{% expression %}{% expression %}
5. TWIG Basics:5. TWIG Basics:
VariablesVariables
Accessing variablesAccessing 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 variablesAssigning 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:6. TWIG Basics:
Control structuresControl 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:7. TWIG Basics:
Filters & FunctionsFilters & 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:8. TWIG Basics:
InheritanceInheritance
<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:9. TWIG Basics:
ExampleExample
{#
/**
* @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!Thank You!
http://slides.com/drubb
http://slideshare.net/drubb

Drupal 8: Theming

  • 1.
    Drupal 8: ThemingDrupal8: Theming Theming basics & TWIG for Drupal 8 modulesTheming basics & TWIG for Drupal 8 modules Drupal Meetup StuttgartDrupal Meetup Stuttgart 07/02/2015
  • 2.
  • 3.
    Once upon atime, in a D7 module...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
  • 4.
    We can dothis a little bit better: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)
  • 5.
    We can dothis much more better: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...
  • 6.
    Voilá, a TemplateEngine!Voilá, a Template Engine! (aka Theme Engine) Drupal 4.7 / 5 / 6 / 7: PHPTemplate Drupal 8: TWIG Variables
  • 7.
    What's wrong withPHPTemplate?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 goesSo Drupal 8 goes PHPTemplatePHPTemplate SmartySmarty PHPTALPHPTAL TWIGTWIG
  • 8.
    2. Building aDrupal 82. Building a Drupal 8 ThemeTheme
  • 9.
    Create a themefolder structureCreate 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/
  • 10.
    Tell Drupal aboutyour themeTell 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
  • 11.
    Tell Drupal aboutcss/js libraries in your themeTell 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
  • 12.
    Add preprocessing stuff,if neededAdd 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!
  • 13.
    Specify breakpoints, ifneededSpecify 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
  • 14.
    Add the remainingstuff...Add the remaining stuff... Templates Scripts Stylesheets Images Logo Screenshot ...
  • 15.
    3. Theming basicsfor3. Theming basics for modulesmodules
  • 16.
    Good News!Good News! Notso many changesNot so many changes Main difference: no more theme() functions!
  • 17.
    Registering templatesRegistering templates (nomore 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
  • 18.
    Create a templateCreatea 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)
  • 19.
    Use this somewhereUsethis somewhere (using render array, not theme()) ... $output = array( '#theme' => 'mymodule_something', '#something' => 'Something', '#otherthing' => 'Otherthing', ); ... Somewhere in your code (controller, plugin, ...)
  • 20.
    Alter some stuffAltersome stuff (mainly provided by other modules) /** * Implements hook_preprocess_HOOK(). */ function yourmodule_preprocess_mymodule_something(&$variables) { ... } In your module file or theme
  • 21.
    4. TWIG Basics:4.TWIG Basics: SyntaxSyntax
  • 22.
    Print / Output: {{some content }}{{ some content }} Comments: {# this is a comment #}{# this is a comment #} Execute statements: {% expression %}{% expression %}
  • 23.
    5. TWIG Basics:5.TWIG Basics: VariablesVariables
  • 24.
    Accessing variablesAccessing variables Simplevariables (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 %}
  • 25.
    Assigning values tovariablesAssigning 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
  • 26.
    6. TWIG Basics:6.TWIG Basics: Control structuresControl structures
  • 27.
    {% 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
  • 28.
    7. TWIG Basics:7.TWIG Basics: Filters & FunctionsFilters & Functions
  • 29.
    {{ 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
  • 30.
    {{ 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
  • 31.
    8. TWIG Basics:8.TWIG Basics: InheritanceInheritance
  • 32.
    <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%}
  • 33.
    9. TWIG Basics:9.TWIG Basics: ExampleExample
  • 34.
    {# /** * @file * Themeoverride 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
  • 35.