Drupal
&

Design System

 

by

Shibin Das

 

www.drupal.org/u/d34dman

BE-FE Conflicts

About

Disconnected

Separated

Disassociated

 

 

We establish boundaries with clear responsibilities.

It also created the WALL.

THEM

YOU

THEM

YOU

Style Guide

+

Component Library

Carbon Design Systems Component library

 

  • Single source of truth
  • Implementation / Technology specific

 

+

Guidelines & Principles

+

Code

+

It is always evolving

Component Library Setup

  1. Node (for yarn install and stuff)
  2. PHP (for composer install and stuff)
  3. Miyagi / Patternlab / Storybook

NOTE: "Miyagi" may be used in examples, but in reality it doesn't matter which Design System is used.

Frontend Developer

Must be Familiar with

  • Miyagi / Patternlab / Storybook
  • Twig
  • Node

 

Bonus

  • Composer

Patternlab.io Website

docs.miyagi.dev Website

Drupal Setup

  1. Drupal 8/9/10
  2. Custom Theme

Drupal Developer

Must be Familiar with

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

Drupal

Why

Component Library

Why

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 convention in your project

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

GREEN

Drupal Template files

teaser-list.html.twig

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

Twig Templates

BLUE

Preprocess Function (Drupal)

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

ORANGE

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

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 design component (in Miyagi) you want to integrate
  2. Identify the Drupal theme "template" you have to integrate
  3. Map the variable from Drupal to design component
    1. Do this mapping either in the drupal theme template
    2. Do the mapping in drupal theme preprocess function

Pros

  • Design System folder and file architecture is independent of Drupal Templates
    • This avoids polluting Drupal "templates" with design system components
  • Design System development, maintenance and testing can be done without depending on Drupal
  • Variable names used in design system doesn't have Drupal lingo like "field_", "nid", "tid", etc.
  • Design system doesn't need to include logic for how the data is "fetched"

Cons

  • Data-structure that Design System uses may not be natural for Drupal Template system 😡😡😡
  • Components may not be compatible with Drupal Entity Templates 😡😡😡😡
  • Difficult for Drupal Developers to understand design system and its guidelines 😡😡
  • Extra step involved in connect Drupal Template with Design system 😡

Key Takeaways

 

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

Data Types

Integrating

Entity and fields

Everything Else

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.html.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.html.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.html.twig

{% set main_content %}
  {{ page.content_pre }}
  {{ page.content }}
  {{ page.content_post }}
{% endset %}
{% include "@templates/default/default.twig" with 
  { 
    content: main_content, 
    messages: messages 
  } only 
%}

page.html.twig

Key Takeaways

 

  • Use "include" to use Design System components

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

@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 component_data only %}

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function bc_utils_preprocess_node_article(&$vars) {
  $vars['component_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['component_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 component_data only %}

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function bc_utils_preprocess_node_article(&$vars) {
  $vars['component_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 component_data %}

bc_utils.module

<?php

/**
 * Preprocess Article Node.
 */
function bc_utils_preprocess_node_article(&$vars) {
  $vars['component_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
    • Node
    • User
    • Block
    • 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.
  • Can't be used for non entity 
    • Form and Form elements
    • Views (unless entity view mode is used to print items)
  • Can't be used for custom theme functions
    • Breadcrumb
    • Menu
  • Can't make things super complicated for complex hierarchy

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.html.twig

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

node--article--teaser.html.twig

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

@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.html.twig

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

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

@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.

 

It aligns witch how Drupal templates works to start with.

 

As a bonus, 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
  • Media items (Image, Responsive image, Video, ..)
  • 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/Textarea)
  • Options (Select/List)
  • Image
  • References (Entity Reference, Media, Paragraph, etc..)

Info

Render Arrays

"Render Array" - What is it?

  • It is an "array"
  • It has info about how it should be printed on page
  • It can be nested
  • It can contain a lot of meta data regarding caching, access check, placeholder replacement, etc.

"Render Array" - Example

$foo = [
  '#type' => 'markup',
  '#markup' => 'I am just a string',
];
{{ foo }}
i am just a string
Render array (php)
Twig Template
HTML Output
$foo = [
  '#type' => 'markup',
  '#markup' => 'I am just a string',
  '#access' => FALSE,
];
{{ foo }}
 
$foo = [
  '#type' => 'markup',
  '#markup' => 'I am just a string',
  '#prefix' => '<div class="foo">',
  '#suffix' => '</div>'
];
{{ foo }}
<div class="foo">i am just a string</div> 

"Render Array" - Example

$foo = t('Hello @name!', ['@name' => 'World']);
{{ foo }}
Hello World!
Render array (php)
Twig Template
HTML Output

Drupal's "t" function generates a render array.

"Render Array" - Typecasting to string

{{ foo|raw }}

Info

Attribute

Integrating

Link / CTA

and other cases

Break the Convention

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

Break the Convention

bc_utils.module

$vars['hasBorder'] = $entity->field_has_border->getValue();

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

@organisms/ste/ste.twig

<div class="Block SimpleTextLink {{ hasBorder ? 'SimpleTextLink--hasBorder' : '' }}">
 ...
</div>

When you check the variable in twig template,

most likely you can't use a render array

Questions?

Thank You

Made with Slides.com