Drupal 8 The Backend of Frontend

Lauri Eskola (lauriii)

Front end track | Twitter: @laurii1

Lauri Eskola

  • Drupal Theme System maintainer
  • Working for Druid
  • I love kittens (added 68 of them to Drupal 8 Core and counting...)
  • ... and I like to break Bartik.

Front end track | Twitter: @laurii1

Drupal Theme System

Provides flexible way to output safe HTML

Drupal 8 ships with simplified theme layer

Drupal 8 - Be lazy!

Laziness is the first step towards efficiency.

 Patrick Bennett

Drupal 7 Template process layer

<?php

function template_process_html(&$variables) {
  // Render page_top and page_bottom into top level variables.
  $variables['page_top'] = drupal_render($variables['page']['page_top']);
  $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']);
  // Place the rendered HTML for the page body into a top level variable.
  $variables['page']              = $variables['page']['#children'];
  $variables['page_bottom'] .= drupal_get_js('footer');

  $variables['head']    = drupal_get_html_head();
  $variables['css']     = drupal_add_css();
  $variables['styles']  = drupal_get_css();
  $variables['scripts'] = drupal_get_js();
}

Wastes resources if something doesn't get printed

Template process layer has been removed.

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

Everything is render array!

Drupal 8

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

Drupal 7

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

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;
}
  
  {%- if items or empty -%}
    <div class="item-list">
      {%- if title is not empty -%}
        <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 %}

Theme functions are being converted to Twig templates...

Drupal 7 vs Drupal 8: 154 / 8

Drupal 8 Theme System pipeline

Code examples:

Drupal 8 Theme System pipeline

hook_theme

  
  <?php

  /**
   * Implements hook_theme().
   */
  function sandwich_theme() {
    return [
      'sandwich' => [
        'variables' => [
          'attributes' => [],
          'name' => '',
          'bread' => '',
          'cheese' => '',
          'veggies' => [],
          'protein' => '',
          'condiments' => [],
        ],
      ],
    ];
  }

Build your render array

  
  <?php

  public function build() {
    return [
      '#theme' => 'sandwich',
      '#name' => $this->t('Chickado'),
      '#attributes' => [
        'id' => 'best-sandwich',
        'class' => ['menu--left', 'clearfix'],
      ],
      '#bread' => $this->t('Sourdough'),
      '#cheese' => $this->t('Gruyère'),
      '#veggies' => [
        $this->t('Avocado'),
        $this->t('Red onion'),
      ],
      '#protein' => $this->t('Chicken'),
      '#condiments' => [
        $this->t('Mayo'),
        $this->t('Dijon'),
      ],
    ];
  }

Drupal 8 Theme System pipeline

Theme suggestions

node.html.twig

node--article.html.twig

hook_theme_suggestion and hook_theme_suggestion_alter

  
  <?php

  /**
   * Implements hook_theme_suggestions_sandwich().
   */
  function sandwich_theme_suggestions_sandwich($variables) {
    return 'sandwich__' . strtolower($variables['name']);
  }
  
  /**
   * Implements hook_theme_suggestions_sandwich_alter().
   */
  function sandwich_theme_suggestions_sandwich_alter(&$suggestions, $variables) {
  }

Drupal 8 Theme System pipeline

template_preprocess and 

hook_preprocess

  
  <?php

  /**
   * Implements template_preprocess_sandwich().
   */
  function template_preprocess_sandwich(&$variables) {
    $variables['name'] = 'Kitten';
  }
  
  /**
   * Implements hook_preprocess_sandwich().
   */
  function sandwich_preprocess_sandwich(&$variables) {
    $variables['name'] = 'Llama';
  }
  
  /**
   * Implements hook_preprocess_sandwich__chickado().
   */
  function sandwich_preprocess_sandwich__chickado(&$variables) {
    $variables['name'] = 'Flamingo';
  }

template_preprocess and 

hook_preprocess

  
  <?php

  /**
   * Implements template_preprocess_sandwich().
   */
  function template_preprocess_sandwich(&$variables) {
    $variables['name'] = t('Kitten');
    if (!empty($variables['name']['#machine_name'])) {
      $variables['name'] = $variables['name']['#machine_name'];
    }
  }
  

Drupal 8 Theme System pipeline

sandwich.html.twig


  <section{{ attributes }}>
    <h2>{{ name }}</h2>
    {% if bread %}
      <p><strong>Bread:</strong> {{ bread }}</p>
    {% endif %}
  
    {% if protein %}
      <p><strong>Protein:</strong> {{ protein }}</p>
    {% endif %}
  
    {% if cheese %}
      <p><strong>Cheese:</strong> {{ cheese }}</p>
    {% endif %}
  
    {% if veggies %}
      <strong>Veggies:</strong>
      <ul>
        {% for veg in veggies %}
          <li>{{ veg }}</li>
        {% endfor %}
      </ul>
    {% endif %}
  
    {% if condiments %}
      <strong>Accoutrement:</strong>
      <ul>
        {% for condiment in condiments %}
          <li>{{ condiment }}</li>
        {% endfor %}
      </ul>
    {% endif %}
  </section>

Download the code:

Twig - what you really need to know.

Twig magic

{{ sandwich.cheese }}

  
  // Array key.
  $sandwich['cheese'];
  // Object property.
  $sandwich->cheese;
  // Also works for magic get (provided you implement magic isset).
  $sandwich->__isset('cheese'); && $sandwich->__get('cheese');
  // Object method.
  $sandwich->cheese();
  // Object get method convention.
  $sandwich->getCheese();
  // Object is method convention.
  $sandwich->isCheese();
  // Method doesn't exist/dynamic method.
  $sandwich->__call('cheese');
              

Twig filters

Meant to manipulate a variable. Takes the first parameter from the variable before "|".


  {% set name = 'Lauri' %}
  {# Print varibale using lenght filter. #}
  {{ name|length }}

Example

Returns

  
  5

Twig functions

Functions with more logic and multiple parameters meant to create simple front-end logic


 {{ dump() }}

Example

Print what you want, when you want

 
 {# We give you what you ask for. #}
 {{ content|without('comments', 'links') }}
              

Drupal 7

Drupal 8

 

 
  <?php
  // We hide the comments and links now so that we can render them later.
  hide($content['comments']);
  hide($content['links']);
  print render($content);

Sometimes what Twig has built inside might not be enough...

How to create a Twig extension

1. Create a Twig extension class


<?php
  /**
   * A class providing my own Twig extension.
   */
  class TrimString extends \Twig_Extension {
    /**
     * {@inheritdoc}
     */
    public function getFilters() {
      return [
        new \Twig_SimpleFilter('trim_string', [$this, 'trimString']),
      ];
    }

    public function trimString($string, $length = 10) {
      return substr($string, 0, $length);
    }
  }
              

2. Tell Twig about your class


  services:
    trim_string.twig.trimstring:
      class: Drupal\trim_string\TwigExtension\TrimString
      tags:
        - { name: twig.extension }
              

trim_string.services.yml

3. Profit! Go to the beach!


  {% set variable = 'my text' %}
  {{ variable|trim_string(2) }}

  my

Returns

Example:

https://github.com/lauriii/trim-string

Autoescape

Markup should live inside a Twig template. Not in PHP!

What is escaping?


  <?php

  use Drupal\Component\Utility\Html

  print Html::escape('Some escaped <em>text</em>');

Prints markup


 Some escaped &lt;em&gt;text&lt;/em&gt;

Some escaped <em>text</em>

What is Autoescaping?


  {{ markup }}

Prints markup


 Some escaped &lt;em&gt;text&lt;/em&gt;

Some escaped <em>text</em>

  
  <?php

  function sandwich_preprocess_sandwich(&$variables) {
    $variables['markup'] = '<em>text</em>';
  }

How to avoid autoescaping?


  {{ markup }}

Prints markup


 Some escaped <em>text</em>

Some escaped text

  
  <?php

  function sandwich_preprocess_sandwich(&$variables) {
    $variables['markup']['#markup'] = '<em>text</em>';
  }

How to avoid autoescaping?


  {{ markup }}

Prints markup


  Some escaped <em>text</em>

Some escaped text


  <?php
  
  use Drupal\Component\Utility\SafeMarkup;
  
  function sandwich_preprocess_sandwich(&$variables) {
    $variables['markup'] = SafeMarkup::format('<em>@txt</em>', ['@txt' => 'text']);
  }

But it has also its caveats...

When autoescaped strings are safe?

When the escaped string is being printed as HTML.

Attributes are NOT HTML!


  <?php
  
  use Drupal\Component\Utility\SafeMarkup;
  
  SafeMarkup::format('<em@txt></em>', ['@txt' => 'text']);
  SafeMarkup::format('<a href="@url"></a>', ['@url' => 'http://kittens.com']);
  SafeMarkup::format('<a href="@url"></a>', ['@url' => 'javascript:alter(0)']);

New placeholder for URLs

  <?php
  
  use Drupal\Component\Utility\SafeMarkup;

  SafeMarkup::format('<a href=":url"></a>', [':url' => 'javascript:alter(0)']);

Autoescaping is only enabled  for Twig templates

Which means using PHP Template or Theme functions is not safe by default

Finishing this API is the only thing still blocking Drupal 8 RC1.

You can support us by testing!

Sprint: Friday

Sprint with the Community on Friday.

We have tasks for every skillset.

Mentors are available for new contributors.

An optional Friday morning workshop for first-time sprinters will help you get set up.

Follow @drupalmentoring.

https://www.flickr.com/photos/amazeelabs/9965814443/in/faves-38914559@N03/

 

Questions?

Front end track | Twitter: @laurii1

DrupalCon 2015 - Drupal 8 The Backend of the Frontend

By lauriii

DrupalCon 2015 - Drupal 8 The Backend of the Frontend

  • 1,565