On Github definitivedrupal / NYCCampTwigTraining
Even in your theme.
<?php
$variables['list'] = theme('item_list', array(
'items' => $items,
));
<?php $variables['list'] = array( '#theme' => 'item_list', '#items' => $items, );
Instead of the list variable being a flat string of HTML, it's a render array that gets rendered in a Twig template using {{ list }}.
Use #attached on a render array instead. (more on this later)
We have them! They use some things from SMACSS and BEM.
Twig is a modern template engine for PHP
…besides the fact that PHP Template is old and "broken"
There are 3 basic block delimiters in Twig:
And one special string interpolation delimiter.
Use filters to modify variables.
Docblock
<?php /** * @file * File description */ ?>
{#
/**
* @file
* File description
*/
#}
File Names
node--article.tpl.php
node--article.html.twig
Function Names
theme_node_links()
node-links.html.twig
…that's right, all theme functions will now be template files!
Printing a variable
<div class="content"><?php print $content; ?></div>
<div class="content">{{ content }}</div>
Printing a hash key item
<?php print $item['#item']['alt']; ?>
{{ item['#item'].alt }}
Assigning a variable
<?php $custom_var = $content->comments; ?>
{% set custom_var = content.comments %}
Assigning an array
<?php $args = array('!author' => $author, '!date' => $created); ?>
{% set args = {'!author': author, '!date': created} %}
<?php if ($content->comments): endif; ?>
{% if content.comments %} {% endif %}
<?php if (!empty($content->comments)): endif; ?>
{% if content.comments is not empty %} {% endif %}
<?php if (isset($content->comments)): endif; ?>
{% if content.comments is defined %} {% endif %}
<?php if ($count > 0): endif; ?>
{% if count > 0 %} {% endif %}
<?php foreach ($users as $user) {} ?>
{% for user in users %} {% endfor %}
Check Plain (escaping)
<?php print check_plain($title); ?>
{{ title|e }}
Translate
<?php print t('Home'); ?>
{{ 'Home'|t }}
Translate w/ substitutions
<?php print t('Welcome, @username', array('@username' => $user->name)); ?>
{{ 'Welcome, @username'|t({'@username': user.name}) }}
{% set username = user.name %}
{% trans %}
Welcome, {{ username }}
{% endtrans %}
Implode list
<?php echo implode(', ', $usernames); ?>
{{ usernames|join(', ') }}
Escape
<?php echo check_plain($title); ?>
{{ title|e }}
{{ title }}
You can use a dot (.) to access attributes of a variable (methods or properties of a PHP object, or items of a PHP array), or the so-called "subscript" syntax ([])
{{ foo.bar }}
{{ foo['#bar'] }}
When the attribute contains special characters (like - that would be interpreted as the minus operator), use the attribute function instead to access the variable attribute:
{# equivalent to the non-working foo.data-foo #}
{{ attribute(foo, 'data-foo') }}
There are still a lot of core theme functions that need your help!
<?php /* @file includes/theme.inc */
function theme_item_list($variables) {
$items = $variables['items'];
$title = $variables['title'];
$type = $variables['type'];
$attributes = $variables['attributes'];
// Only output the list container and title, if there are any list items.
// Check to see whether the block title exists before adding a header.
// Empty headers are not semantic and present accessibility challenges.
$output = '<div class="item-list">';
if (isset($title) && $title !== '') {
$output .= '<h3>' . $title . '</h3>';
}
if (!empty($items)) {
$output .= "<$type" . drupal_attributes($attributes) . '>';
$num_items = count($items);
$i = 0;
foreach ($items as $item) {
$attributes = array();
$children = array();
$data = '';
$i++;
if (is_array($item)) {
foreach ($item as $key => $value) {
if ($key == 'data') {
$data = $value;
}
elseif ($key == 'children') {
$children = $value;
}
else {
$attributes[$key] = $value;
}
}
}
else {
$data = $item;
}
if (count($children) > 0) {
// Render nested list.
$data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));
}
if ($i == 1) {
$attributes['class'][] = 'first';
}
if ($i == $num_items) {
$attributes['class'][] = 'last';
}
$output .= '<li' . drupal_attributes($attributes) . '>' . $data . "</li>\n";
}
$output .= "</$type>";
}
$output .= '</div>';
return $output;
}?>
{# /core/modules/system/templates/item-list.html.twig #}
{%- if items or empty -%}
<div class="item-list">
{%- if title -%}
<h3>{{ title }}</h3>
{%- endif -%}
{%- if items -%}
<{{ list_type }}{{ attributes }}>
{%- for item in items -%}
<li{{ item.attributes }}>{{ item.value }}</li>
{%- endfor -%}
</{{ list_type }}>
{%- else -%}
{{- empty -}}
{%- endif -%}
</div>
{%- endif %}
<?php /* includes/theme.inc */
function theme_links($variables) {
$links = $variables['links'];
$attributes = $variables['attributes'];
$heading = $variables['heading'];
global $language_url;
$output = '';
if (count($links) > 0) {
$output = '';
// Treat the heading first if it is present to prepend it to the
// list of links.
if (!empty($heading)) {
if (is_string($heading)) {
// Prepare the array that will be used when the passed heading
// is a string.
$heading = array(
'text' => $heading,
// Set the default level of the heading.
'level' => 'h2',
);
}
$output .= '<' . $heading['level'];
if (!empty($heading['class'])) {
$output .= drupal_attributes(array('class' => $heading['class']));
}
$output .= '>' . check_plain($heading['text']) . '<!--' . $heading['level'] . '-->';
}
$output .= '<ul' . drupal_attributes($attributes) . '>';
$num_links = count($links);
$i = 1;
foreach ($links as $key => $link) {
$class = array($key);
// Add first, last and active classes to the list of links to help out themers.
if ($i == 1) {
$class[] = 'first';
}
if ($i == $num_links) {
$class[] = 'last';
}
if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page())) && (empty($link['language']) || $link['language']->language == $language_url->language)) {
$class[] = 'active';
}
$output .= '<li' . drupal_attributes(array('class' => $class)) . '>';
if (isset($link['href'])) {
// Pass in $link as $options, they share the same keys.
$output .= l($link['title'], $link['href'], $link);
}
elseif (!empty($link['title'])) {
// Some links are actually not links, but we wrap these in <span> for adding title and class attributes.
if (empty($link['html'])) {
$link['title'] = check_plain($link['title']);
}
$span_attributes = '';
if (isset($link['attributes'])) {
$span_attributes = drupal_attributes($link['attributes']);
}
$output .= '<span' . $span_attributes . '>' . $link['title'] . '</span>';
}
$i++;
$output .= "</li>\n";
}
$output .= '</ul>';
}
return $output;
}?>
{# /core/modules/system/templates/links.html.twig #}
{% if links -%}
{%- if heading -%}
{%- if heading.level -%}
<{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}>
{%- else -%}
<h2{{ heading.attributes }}>{{ heading.text }}</h2>
{%- endif -%}
{%- endif -%}
<ul{{ attributes }}>
{%- for item in links -%}
<li{{ item.attributes }}>
{%- if item.link -%}
{{ item.link }}
{%- elseif item.text_attributes -%}
<span{{ item.text_attributes }}>{{ item.text }}</span>
{%- else -%}
{{ item.text }}
{%- endif -%}
</li>
{%- endfor -%}
</ul>
{%- endif %}
<?php /* includes/theme.inc */
function theme_breadcrumb($variables) {
$breadcrumb = $variables['breadcrumb'];
if (!empty($breadcrumb)) {
// Provide a navigational heading to give context for breadcrumb links to
// screen-reader users. Make the heading invisible with .element-invisible.
$output = '<h2 class="element-invisible">' . t('You are here') . '</h2>';
$output .= '<div class="breadcrumb">' . implode(' >> ', $breadcrumb) . '</div>';
return $output;
}
}?>
{# /core/modules/system/templates/breadcrumb.html.twig #}
{% if breadcrumb %}
<nav class="breadcrumb" role="navigation">
<h2 class="visually-hidden">{{ 'You are here'|t }}</h2>
<ol>
{% for item in breadcrumb %}
<li>{{ item }}</li>
{% endfor %}
</ol>
</nav>
{% endif %}
Developers can change the environment variables in settings.php to allow for different configuration per environment. Drupal core uses the Settings API to access this information when building the Twig environment.
$settings['twig_debug'] = TRUE;
Twig Documentation is easy to get to, just like it is for PHP.net
Reusable markup templates with variable insertion
Think: PHP Template functions for markup
{# menu.twig #}
{% macro menu_links(links) %}
{% if links %}
<ul>
{% for link in links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
{% if link.links %}
{{ _self.menu_links(link.links) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{# somewhere in a twig template in our theme #}
{% import "menu.twig" as menu %}
<nav>
{{ menu.menu_links(links) }}
</nav>