Drupal
&

Patternlab


by

Shibin Das


www.drupal.org/u/d34dman

About

Patterns

&

Quality Control

Printed Circuit Board

Could you spot an odd one out?

Text

Orienting the components in a similar manner help prevent assembly error

Or at least makes spotting errors much easier.

Text

Following a convention helps in

quality control

Having a wide collection of screwdrivers doesn't justify the use of wide types of screwdrivers

Key Takeaways

 

  • Have some sort of convention in your project

Drupal Setup

  1. Drupal 7
  2. tfd7 (Twig for D7)
  3. Custom theme using tfd7
  4. Panels, Panelizer, Panlizer IPE, Fieldable Panels Pane (FPP), Paragraphs for content authoring
  5. Custom module for utility functions

 

Drupal Developer

Must be Familiar with

  • Entities
  • Fields
  • Template Files
  • Template Preprocessing
  • Field Formatters
  • View Modes
  • Render array

 

Bonus

  • Panels and Panelizer
  • Fieldable Panel Panes and Paragraphs

Twig for Drupal 7 Website

Patternlab Setup

  1. Node
  2. PHP

Frontend Developer

Must be Familiar with

  • Patternlab
  • Twig

 

Bonus

  • Composer

Patternlab.io Website

Before we Proceed

Mockups

Using Balsamiq Mockups 3

@template/teaser-list/teaser-list.twig

<div class="TeaserList">
  {% for item in items %}
    <div class="TeaserList-item">
      {{ item|raw }}
    </div>
  {% endfor %}
</div>

Patternlab files

Twig templates

Drupal Template files

teaser-list.tpl.twig

{% include "@template/teaser-list/teaser-list.twig" with teaser_list_data %}

Twig Templates

Drupal Module/Theme Preprocessing Files

bc_utils.module

<?php

/**
 * Implements hook_block_view().
 */
function bc_utils_block_view($delta) {
  $block = [];
  switch ($delta) {
    case 'teaser_list':

      // Retrieve list of article nodes using Views or Entity Field Query.
      $article_nodes = _bc_utils_get_article_teaser_list_nodes($max_items = 3);
      $items = [];
      foreach ($article_nodes as $article_node) {
        $items[] = node_view($article_node, 'teaser');
      }

      // Prepare block render array.
      $block['subject'] = t('Teaser List');
      $block['content'] = [
        '#theme' => 'teaser_list',
        '#teaser_list_data' => [
          'items' => $items,
        ],
      ];
  }
  return $block;
}

PHP Code

Drupal UI Configurations

Click Click Click

  • An entity type is a base class
  • A bundle is an extended class
  • A field is a class member, property, variable or field instance (depending on your naming preference)
  • An entity is an object or instance of a base or extended class

Entity Type

Base Class

  • Node
  • User
  • Comments
  • Taxonomy Term
  • Vocabulary
  • Files

Bundle

Extended Class

  • Node (Content Types)
    • Article
    • Page
  • User
    • User
  • Taxonomy Term
    • Tags
    • Pathology

Field

Property

  • Node
    • Article
      • Title
      • Description
      • Image
    • Page
      • Title
      • Related Article
      • Tag
      • Copy

Entity

Object

  • Node : About : My Dog is black
  • Node : Blog : My Cat has issues
  • Node : Blog : Two beds and a coffee machine
  • Tag : Animal : Feline
  • Tag : Animal : Canidae

Intimidating

yet?

Human Mind is amazing

 

because it Ignores

Basics

Integrating

Lets say you have some designs that needs to be integrated into Drupal

Frontend Developers (FE) starts working in Patternlab

Get your components reviewed in Patternlab with demo data

Use Drupal Templates to link Patternlab components with Drupal

Pass variables from Drupal to Patternlab through theme_preprocess functions

  1. Identify the patternlab component you want to integrate
  2. Identify the Drupal theme "template" you have to integrate
  3. In a preprocess function for the Drupal theme, populate the variables as required by the patternlab component.
  4. Map drupal theme template to patternlab component

Key Takeaways

 

  • Frontend Developers don't need Drupal setup to work.
  • Use Drupal Templates as liaison between Patternlab and Drupal 

Types of Data

Integrating

Entity

  • Content
  •  

Custom

  • Forms
  • Menu
  • Date
  • Static Texts
  • Computed Data
  •  

Page

Integrating

Header

Footer

Sidebar

Content

Breadcrumb

<!DOCTYPE html>
<html lang="{{ language.language }}" dir="{{ language.dir }}">
<head>
  {{ head }}
  <title>{{ head_title }}</title>
  {{ styles }}
</head>
<body class="{{ classes }}" {{ attributes }}>

  <div class="u-hiddenVisually" id="skip-link">
    <a href="#main-content" class="element-invisible element-focusable">
        {{ 'Skip to main content'|t }}
    </a>
  </div>

  {{ page_top }}
  {{ page }}
  {{ page_bottom }}
  {{ scripts }}

</body>
</html>

html.tpl.twig


{% include '@template-components/header/header.twig' %}

{% include '@template-components/messages/messages.twig' with messages %}

  {{ page.content_pre }}
  {{ page.content }}
  {{ page.content_post }}

{% include '@template-components/footer/footer.twig' %}

page.tpl.twig

{% extends "@templates/default/default.twig" %}

{% block messages %}
  {% include '@template-components/messages/messages.twig' with messages %}
{% endblock %}

{% block content %}
  {{ page.content_pre }}
  {{ page.content }}
  {{ page.content_post }}
{% endblock content %}

page.tpl.twig

{% extends "@templates/default/default.twig" %}

{% block messages %}
  {% include '@template-components/messages/messages.twig' with messages %}
{% endblock %}

{% block sidebar %}
  {{ sidebar }}
{% endblock sidebar %}

{% block content %}
  {{ page.content_pre }}
  {{ page.content }}
  {{ page.content_post }}
{% endblock content %}

page.tpl.twig

Key Takeaways

 

  • Use "extends" to extend a layout template in patternlab.

Why not use "include" always

Entity

Integrating

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-title">
      {{ title }}
    </h1>
    <div class="Article-subTitle">
      {{ sub_title }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-video">
      {{ video }}
    </div>
    <div class="Article-description">
      {{ description }}
    </div>
  </div>
</div>

@template/article/article.twig

@template/article/article.twig

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-title">
      {{ title|raw }}
    </h1>
    <div class="Article-subTitle">
      {{ sub_title|raw }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-video">
      {{ video|raw }}
    </div>
    <div class="Article-description">
      {{ description|raw }}
    </div>
  </div>
</div>

node--article.tpl.twig

{% include "@template/article/article.twig" with patternlab_data %}

@template/article/article.twig

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-title">
      {{ title|raw }}
    </h1>
    <div class="Article-subTitle">
      {{ sub_title|raw }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-video">
      {{ video|raw }}
    </div>
    <div class="Article-description">
      {{ description|raw }}
    </div>
  </div>
</div>

WITH ONLY!!!!!

@template/article/article.twig

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-title">
      {{ title|raw }}
    </h1>
    <div class="Article-subTitle">
      {{ sub_title|raw }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-video">
      {{ video|raw }}
    </div>
    <div class="Article-description">
      {{ description|raw }}
    </div>
  </div>
</div>

node--article.tpl.twig

{% include "@template/article/article.twig" with patternlab_data %}

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function bc_utils_preprocess_node_article(&$vars) {
  $vars['patternlab_data'] = [
    'title' => $vars['node']->title,
    'sub_title' => $vars['content']['field_article_sub_title'],
    'video' => $vars['content']['field_article_video'],
    'description' => $vars['content']['field_article_description'],
  ];
}

@template/article/article.twig

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-title">
      {{ title|raw }}
    </h1>
    <div class="Article-subTitle">
      {{ sub_title|raw }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-video">
      {{ video|raw }}
    </div>
    <div class="Article-description">
      {{ description|raw }}
    </div>
  </div>
</div>

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function bc_utils_preprocess_node_article(&$vars) {
  $vars['patternlab_data'] = [
    'title' => $vars['node']->title,
    'sub_title' => $vars['content']['field_article_sub_title'],
    'video' => $vars['content']['field_article_video'],
    'description' => $vars['content']['field_article_description'],
  ];
}

@template/article/article.twig

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-title">
      {{ title|raw }}
    </h1>
    <div class="Article-subTitle">
      {{ sub_title|raw }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-video">
      {{ video|raw }}
    </div>
    <div class="Article-description">
      {{ description|raw }}
    </div>
  </div>
</div>

node--article.tpl.twig

{% include "@template/article/article.twig" with patternlab_data %}

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function bc_utils_preprocess_node_article(&$vars) {
  $vars['patternlab_data'] = [
    'title' => $vars['node']->title,
    'sub_title' => $vars['content']['field_article_sub_title'],
    'video' => $vars['content']['field_article_video'],
    'description' => $vars['content']['field_article_description'],
  ];
}

@template/article/article.twig

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-page-title">
      {{ title | raw }}
    </h1>
    <div class="Article-page-subtitle">
      {{ sub_title | raw }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-content-video">
      {{ video | raw }}
    </div>
    <div class="Article-content-description">
      {{ description | raw }}
    </div>
  </div>
</div>

node--article.tpl.twig

{% include "@template/article/article.twig" with patternlab_data %}

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function sscild_frontend_preprocess_node_article(&$vars) {
  $vars['patternlab_data'] = [
    'title' => $vars['node']->title,
    'sub_title' => $vars['content']['field_article_sub_title'],
    'video' => $vars['content']['field_article_video'],
    'description' => $vars['content']['field_article_description'],
  ];
}

@template/article/article.twig

<div class="Article">
  <div class="Article-header">
    <h1 class="Article-title">
      {{ title|raw }}
    </h1>
    <div class="Article-subTitle">
      {{ sub_title|raw }}
    </div>
  </div>
  <div class="Article-content">
    <div class="Article-video">
      {{ video|raw }}
    </div>
    <div class="Article-description">
      {{ description|raw }}
    </div>
  </div>
</div>

node--article.tpl.twig

{% include "@template/article/article.twig" with patternlab_data %}

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function bc_utils_preprocess_node_article(&$vars) {
  $vars['patternlab_data'] = [
    'title' => $vars['node']->title,
    'sub_title' => $vars['content']['field_article_sub_title'],
    'video' => $vars['content']['field_article_video'],
    'description' => $vars['content']['field_article_description'],
  ];
}

Advantage of this strategy

 

  • Can be used for all entities that have fields attached via Field API and supports View Modes
    • Node
    • User
    • Fieldable Panels Pane
    • Paragraphs
  • Respects Language
  • Respects Access
  • Uses Field Formatters
  • Locale Aware

Dis-Advantage of this strategy

 

  • Extra wrappers around string thanks to render array and field formatters.

Key Takeaways

 

  • Use "view modes"
  • Use "field formatters"

List

Integrating

@template/teaser-list/teaser-list.twig

<div class="TeaserList">
  {% for item in items %}
    <div class="TeaserListt-item">
      <div class="TeaserList-item-Article-teaser">
        <h3>{{ item.title | raw }}</h3>
        <h4>{{ item.sub_title | raw}}</h4>
        <div class="TeaserList-item-Article-teaser-description">
          {{ item.description | raw }}
        </div>
      </div>
    </div>
  {% endfor %}
</div>

@template/teaser-list/teaser-list.twig

<div class="TeaserList">
  {% for item in items %}
    <div class="TeaserList-item">
      {{ item | raw }}
    </div>
  {% endfor %}
</div>

@template/article-teaser/article-teaser.twig

<div class="Article-teaser">
  <h3>{{ title | raw }}</h3>
  <h4>{{ sub_title | raw}}</h4>
  <div class="Article-teaser-description">
    {{ description | raw }}
  </div>
</div>

block--teaser-list.tpl.twig

{% include "@template/teaser-list/teaser-list.twig" with teaser_list %}

node--article--teaser.tpl.twig

{% include "@template/article-teaser/article-teaser.twig" with patternlab_data %}

@template/teaser-list/teaser-list.twig

<div class="TeaserList">
  {% for item in items %}
    <div class="TeaserList-item">
      {{ item | raw }}
    </div>
  {% endfor %}
</div>

@template/article-teaser/article-teaser.twig

<div class="ArticleTeaser">
  <h3>{{ title | raw }}</h3>
  <h4>{{ sub_title | raw}}</h4>
  <div class="ArticleTeaser-description">
    {{ description | raw }}
  </div>
</div>

node--article--teaser.tpl.twig

{% include "@template/article-teaser/article-teaser.twig" with patternlab_data %}

@template/article-teaser/article-teaser.twig

<div class="ArticleTeaser">
  <h3>{{ title | raw }}</h3>
  <h4>{{ sub_title | raw}}</h4>
  <div class="ArticleTeaser-description">
    {{ description | raw }}
  </div>
</div>

teaser-list.tpl.twig

{% include "@template/teaser-list/teaser-list.twig" with teaser_list_data %}

@template/teaser-list/teaser-list.twig

<div class="TeaserList">
  {% for item in items %}
    <div class="TeaserList-item">
      {{ item | raw }}
    </div>
  {% endfor %}
</div>

bc_utils.module

<?php

/**
 * Implements hook_block_view().
 */
function bc_utils_block_view($delta) {
  $block = [];
  switch ($delta) {
    case 'teaser_list':

      // Retrieve list of article nodes using Views or Entity Field Query.
      $article_nodes = _bc_utils_get_article_teaser_list_nodes($max_items = 3);
      $items = [];
      foreach ($article_nodes as $article_node) {
        $items[] = node_view($article_nodes, 'teaser');
      }

      // Prepare block render array.
      $block['subject'] = t('Teaser List');
      $block['content'] = [
        '#theme' => 'teaser_list',
        '#teaser_list_data' => [
          'items' => $items,
        ],
      ];
  }
  return $block;
}

teaser-list.tpl.twig

{% include "@template/teaser-list/teaser-list.twig" with teaser_list_data %}

@template/teaser-list/teaser-list.twig

<div class="Teaser-list">
  {% for item in items %}
    <div class="Teaser-list-item">
      {{ item | raw }}
    </div>
  {% endfor %}
</div>

bc_utils.module

<?php

/**
 * Implements hook_block_view().
 */
function bc_utils_block_view($delta) {
  $block = [];
  switch ($delta) {
    case 'teaser_list':

      // Retrieve list of article nodes using Views or Entity Field Query.
      $article_nodes = _bc_utils_get_article_teaser_list_nodes($max_items = 3);
      $items = [];
      foreach ($article_nodes as $article_node) {
        $items[] = node_view($article_nodes, 'teaser');
      }

      // Prepare block render array.
      $block['subject'] = t('Teaser List');
      $block['content'] = [
        '#theme' => 'teaser_list',
        '#teaser_list_data' => [
          'items' => $items,
        ],
      ];
  }
  return $block;
}

teaser-list.tpl.twig

{% include "@template/teaser-list/teaser-list.twig" with teaser_list_data %}

@template/teaser-list/teaser-list.twig

<div class="TeaserList">
  {% for item in items %}
    <div class="TeaserList-item">
      {{ item | raw }}
    </div>
  {% endfor %}
</div>

bc_utils.module

<?php

/**
 * Implements hook_block_view().
 */
function bc_utils_block_view($delta) {
  $block = [];
  switch ($delta) {
    case 'teaser_list':

      // Retrieve list of article nodes using Views or Entity Field Query.
      $article_nodes = _bc_utils_get_article_teaser_list_nodes($max_items = 3);
      $items = [];
      foreach ($article_nodes as $article_node) {
        $items[] = node_view($article_nodes, 'teaser');
      }

      // Prepare block render array.
      $block['subject'] = t('Teaser List');
      $block['content'] = [
        '#theme' => 'teaser_list',
        '#teaser_list_data' => [
          'items' => $items,
        ],
      ];
  }
  return $block;
}

Advantage of this Strategy

 

Making List component agnostic of its embedded items opens up a lot of possibility.

 

For example we can now use the same teaser-list component to show list of teasers of different content types. These content types can have different fields and teaser template.

Advantage of this Strategy


The same logic can be applied for rendering fields which embed content on entities through

  • Entity Reference
  • Node Reference
  • Taxonomy Reference
  • Paragraphs

Key Takeaways

 

  • Make Parent component agnostic of embedded components.

Integrating

Fields

Mapping a text field in preprocess().

/**
 * Implements hook_preprocess_node().
 */
function my_module_preprocess_node(&$vars) {
    // A simple text field can be found in following places in variable $vars.

    // Inside the entity object.
    // CASE 1:
    $description = $vars['elements']['#node']->field_description['und'][0]['value'];
    // CASE 2:
    $description = $vars['elements']['#node']->field_description['und'][0]['safe_value'];
    
    // The following is a render array.
    // Present if the particular viewmode is configured to show the field.
    // CASE 3:
    $description = $vars['content']['field_description'];
}

Mapping a text field in preprocess().

/**
 * Implements hook_preprocess_node().
 */
function my_module_preprocess_node(&$vars) {
    // A simple text field can be found in following places in variable $vars.

    // Inside the $vars as fields, present when there is a value stored in database.
    // CASE 4:
    $description = $vars['field_description'][0]['value'];
    // CASE 5:
    $description = $vars['field_description'][0]['safe_value'];

}

Mapping a text field in preprocess().

/**
 * Implements hook_preprocess_node().
 */
function my_module_preprocess_node(&$vars) {
    // A simple text field can be found in following places in variable $vars.

    // You can use entity_metadata_wrapper to retrieve the property.
    try {
      $node = $vars['elements']['#node'];
      $wrapper = entity_metadata_wrapper('node', $node);
      // CASE 6:
      $description = $wrapper->field_description->value();
      // Which also allows you to fetch referenced entity and their properties too.
      foreach ($wrapper->field_taxonomy_terms->getIterator() as $delta => $term_wrapper) {
        $label = $term_wrapper->name->value();
      }
    } 
    catch (EntityMetadataWrapperException $exc) {
      $trace = '<pre>' . $exc->getTraceAsString() . '</pre>';
      watchdog(
        ‘debug’,
        $trace,
        NULL,
        WATCHDOG_ERROR
      );
    }

}

Mapping a text field in preprocess().

/**
 * Implements hook_preprocess_node().
 */
function my_module_preprocess_node(&$vars) {
    // A simple text field can be found in following places in variable $vars.

    // ---------------------- //
    // Or you might even do a db_query/db_select.
    // CASE 7:
}

Single Valued Fields

bc_utils.module

    $vars['sub_title'] = $vars['content']['field_article_sub_title'];

Pass the render array of the field found under $vars['content']

@template/article/article.twig

      {{ sub_title | raw }}

In patternlab component print the variable using "raw"

Field Item 1

Field Wrapper

{{ field_article text }}

Field Item 1

Field Wrapper

{{ field_article text }}

Field Item 2

Field Item 3

Field Item 1

Field Wrapper

{{ field_article text }}

Field Item 2

Field Wrapper

Field Item 2

Field Wrapper

Multi Valued Fields

bc_utils.module

  $vars['references'] = array();
  if (!empty($vars['content']['field_references'])) {
    foreach (element_children($vars['content']['field_references']) as $delta) {
      $vars['references'][] = $vars['content']['field_references'][$delta];
    }
  }

Pass the render array of the field found under $vars['content']

after a the following adjustment

@template/article/article.twig

  <div class="ArticleReferences-list">
    {% for reference in references %}
      <div class="ArticleReference-list-item">
        {{ reference | raw }}
      </div>
    {% endfor %}
  </div>

This will allow access to multivalued field as an array in patternlab component

Following fields can use the strategy

 

  • Text (Simple/Rich/Number)
  • Options (Select/List)
  • Image
  • References (Entity Reference, Paragraph, etc..)

Integrating

Link / CTA

Break the Convention

bc_utils.module

$vars['cta'] = array(
  'label' =>  $vars['fpp_rel_story_cta'][0]['title'],
  'url' => url($vars['fpp_rel_story_cta'][0]['url']),
);

BY NOT Passing the render array of the field found under $vars['content']

@organisms/ste/ste.twig

<div class="Block SimpleTextLink">
  {% include "@atoms/button/button.twig" with {
    url: cta.url,
    label: cta.label,
    target: "_blank"
  } %}
</div>

In patternlab component you can access the variable using custom property

Questions?

Thank You

Drupal 7 and Patternlab

By shibin das

Drupal 7 and Patternlab

Working with Drupal 7 and Patternlab

  • 517