NYC Camp 2014 – Jump into Twig! – Joël Pittet



NYC Camp 2014 – Jump into Twig! – Joël Pittet

1 3


NYCCampTwigTraining

Presentation for Twig Training at MidCamp and NYC Camp

On Github definitivedrupal / NYCCampTwigTraining

NYC Camp 2014

Jump into Twig!

Joël Pittet & Scott Reeves

joelpittet

Joël Pittet

  • Building Websites since 2001ish
  • Front and Back End Developer
  • Drupal 8 Core Theme System Maintainer
  • And obviously love perogies

Scott Reeves

Cottser
  • Developer at Digital Echidna
  • Drupal 8 theme system co-maintainer
  • Drupal core mentor
  • Likes beans

Drupal 8 theming changes

YAML

Even in your theme.

Process layer

template.php

THEMENAME.theme

Goodbye theme(), hello render arrays

Before:

<?php
$variables['list'] = theme('item_list', array(
  'items' => $items,
));

After:

<?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 }}.

No more:

drupal_add_css()

drupal_add_js()

drupal_add_library()

Use #attached on a render array instead. (more on this later)

CSS standards

We have them! They use some things from SMACSS and BEM.

https://drupal.org/node/1886770

Less IE support

IE6

IE7

IE8

https://drupal.org/project/ie8

What is Twig?

Twig is a modern template engine for PHP

Why another template engine?

…besides the fact that PHP Template is old and "broken"

  • Concise
  • Template Oriented Syntax
  • Full Featured
  • Easy to Learn
  • Extensible
  • Unit Tested
  • Documented
  • Secure
  • Clear Error Messages
  • Fast

Twig Basics

There are 3 basic block delimiters in Twig:

  • {{ foo.bar }}
  • {% set var = fubar %}
  • {# comments comments comments #}

And one special string interpolation delimiter.

  • #{}
  • {{ "foo #{bar} baz" }}
  • {{ "foo #{1 + 2} baz" }}

Use filters to modify variables.

Twig Filters
Drupal Filters & Functions
  • Translate
    • {{ form.label|t }}
  • Hide/Remove
    • {{ attribute|without('class') }}
  • URL [changes to an alias]
    • {{ url('node/1') }}

PHP Template – vs – Twig

Docblock

<?php
/**
 * @file
 * File description
 */
?>
{#
/**
 * @file
 * File description
 */
#}

File & Function names

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!

Variables

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} %}

Conditionals

<?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 %}

Control Structures

<?php foreach ($users as $user) {} ?>
{% for user in users %} {% endfor %}

Filters

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 }}

Data Interaction

Dot Notation FTW!

 

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') }}

Theme Functions

There are still a lot of core theme functions that need your help!

Item List

<?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 %}

Links

<?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 %}

Breadcrumb

<?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 %}

Twig Settings

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.

Twig Debug

$settings['twig_debug'] = TRUE;

Twig documentation tip

Twig Documentation is easy to get to, just like it is for PHP.net

  • http://php.net/strtolower
  • http://twig.sensiolabs.org/lower

Let’s have some fun!

Macros

Reusable markup templates with variable insertion

Think: PHP Template functions for markup

Macro Example: Menu links

{# 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>

How’d we do?

http://nyctraining.drupalgardens.com