On Github DrupalTwig / ThemeSystemBadCamp2014
1:30 - 5:00
Now we can hit 'B' and gauge the personas in the audience. Ask people to raise their hand if they are:
…is gone!
5:00 - 8:00
…are being converted to Twig templates…(and other formats).
8:00 - 11:00
Theme functions are so Drupal 7 (effectively deprecated, people will yell at you) Markup belongs in templates, not in PHP strings. Your IDE/Text Editor will reward you by syntax highlighting markup and much less string concatenation and quote escaping is needed. We have 20 left to convert.Drupal 7:
<?php /** * Implements hook_preprocess_HOOK() for node templates. */ function MYTHEME_preprocess_node(&$variables) { $variables['theme_hook_suggestions'][] = 'node__' . 'my_first_suggestion'; $variables['theme_hook_suggestions'][] = 'node__' . 'my_second_more_specific_suggestion'; }
Drupal 8:
<?php /** * Implements hook_theme_suggestions_HOOK_alter() for node templates. */ function MYTHEME_theme_suggestions_node_alter(array &$suggestions, array $variables) { $suggestions[] = 'node__' . 'my_first_suggestion'; $suggestions[] = 'node__' . 'my_second_suggestion'; }
11:00 - 14:00
In Drupal 7 you could manipulate theme suggestions during preprocess, but by this point it might already be too late, or you've already done unnecessary preprocessing. Drupal 8 splits out the manipulation of theme hook (template) suggestions into separate hooks. You can see template suggestions in action when we talk about Twig debug in a few minutes.<?php $variables['list'] = theme('item_list', array( 'items' => $items, ));
<?php $variables['list'] = array( '#theme' => 'item_list', '#items' => $items, );
14:00 - 16:00
Instead of the list variable being a flat string of HTML, it's a render array that gets passed to the template and rendered from there.
<div{{ attributes }}>
<div {{ attributes }}>
<div✖{{ attributes }}>
<div class="myclass {{ attributes.class }}"{{ attributes|without('class') }}>
16:00 - 19:00
Attributes contain the attributes and also know how to print them properly. If you are printing the entire set of attributes, don't prepend a space.
<div{{ attributes.addClass('hello').removeClass('goodbye') }}>
{% if attributes.hasClass('field-label-inline') %} {# Do something special here. #} {% endif %}
19:00 - 21:00
<?php // We hide the comments and links now so that we can render them later. hide($content['comments']); hide($content['links']); print render($content); ?>
{# We give you what you ask for. #} {{ content|without('comments', 'links') }}
21:00 - 23:00
23:00 - 25:00
services.yml:
parameters: twig.config: debug: true
Example output:
<!-- THEME DEBUG --> <!-- CALL: _theme('block') --> <!-- FILE NAME SUGGESTIONS: * block--bartik-powered.html.twig * block--system-powered-by-block.html.twig * block--system.html.twig x block.html.twig --> <!-- BEGIN OUTPUT from 'core/modules/block/templates/block.html.twig' --> <div class="block block-system contextual-region" id="block-bartik-powered" role="complementary"> <div data-contextual-id="block:block=bartik_powered:"></div> <div class="content"> <span>Powered by <a href="http://drupal.org">Drupal</a></span> </div> </div> <!-- END OUTPUT from 'core/modules/block/templates/block.html.twig' -->
25:00 - 27:00
settings.php:
$conf['theme_debug'] = TRUE;
Example output:
<!-- THEME DEBUG --> <!-- CALL: theme('block') --> <!-- FILE NAME SUGGESTIONS: * block--system--powered-by.tpl.php * block--system.tpl.php * block--footer.tpl.php x block.tpl.php --> <!-- BEGIN OUTPUT from 'modules/block/block.tpl.php' --> <div id="block-system-powered-by" class="block block-system"> <div class="content"> <span>Powered by <a href="https://www.drupal.org">Drupal</a></span> </div> </div> <!-- END OUTPUT from 'modules/block/block.tpl.php' -->
27:00 - 28:00
https://github.com/DrupalTwig/sandwich
28:00 - 29:00
We were hungry when we came up with this example.
<?php /** * Implements hook_theme(). */ function sandwich_theme() { return array( 'sandwich' => array( 'variables' => array( 'attributes' => array(), 'name' => '', 'bread' => '', 'cheese' => '', 'veggies' => array(), 'protein' => '', 'condiments' => array(), ), // 'template' is the default in Drupal 8! 'template' => 'sandwich', ), ); }
<?php /** * Implements hook_theme(). */ function sandwich_theme() { return [ 'sandwich' => [ 'variables' => [ 'attributes' => [], 'name' => '', 'bread' => '', 'cheese' => '', 'veggies' => [], 'protein' => '', 'condiments' => [], ], ], ]; }
<?php /** * Builds a sandwich. */ public function build() { return [ '#theme' => 'sandwich', '#name' => $this->t('Chickado'), '#attributes' => [ 'id' => 'best-sandwich', 'style' => 'float: left;', 'class' => ['left', 'clearfix'], ], '#bread' => $this->t('Sourdough'), '#cheese' => $this->t('Gruyère'), '#veggies' => [$this->t('Avocado'), $this->t('Red onion'), $this->t('Romaine')], '#protein' => $this->t('Chicken'), '#condiments' => [$this->t('Mayo'), $this->t('Dijon')], ]; }
Pass in variables using #-prefixed keys.
- 31:00
<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>Vegetables:</strong> <ul> {% for veg in veggies %} <li>{{ veg }}</li> {% endfor %} </ul> {% endif %} {% if condiments %} <strong>Condiments:</strong> <ul> {% for condiment in condiments %} <li>{{ condiment }}</li> {% endfor %} </ul> {% endif %} </section>
That's the bare minimum for most practical use cases. But what if you need to do more, or if you're altering themeable output that someone else has defined? Let's dig a bit deeper.
Don't make a template for condiments. Real life example: toolbar.
33:00 - 38:00
drupal_render() is called on the render array. Any defined #pre_render callbacks are called. drupal_render() calls the now internal _theme() function.
After _theme() does its thing, #post_render callbacks can massage the resulting string.
{{ 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');
38:00 - 40:00
@Cottser
@joelpittet
IRC: #drupal-twig
Twitter: #drupaltwig