Emulsify 2: Building a Full Site Header in Drupal
One of the most common requests we get in regards to Emulsify is to show concrete examples of components. There is a lot of conceptual material out there on the benefits of component-driven development in Drupal 8—storing markup, CSS, and JavaScript together using some organizational pattern (à la Atomic Design), automating the creation of style guides (e.g., using Pattern Lab) and using Twig’s include
, extends
and embed
functions to work those patterns into Drupal seamlessly. If you’re reading this article you’re likely already sold on the concept. It’s time for a concrete example!
In this tutorial, we’ll build a full site header containing a logo, a search form, and a menu – here’s the code if you’d like to follow along. We will use Emulsify, so pieces of this may be specific to Emulsify and we will try and note those where necessary. Otherwise, this example could, in theory, be extended to any Drupal 8 project using component-driven development.
Planning Your Component
The first step in component-driven development is planning. In fact, this may be the definitive phase in component-driven development. In order to build reusable systems, you have to break down the design into logical, reusable building blocks. In our case, we have 3 distinct components—what we would call in Atomic Design “molecules”—a logo, a search form, and a menu. In most component-driven development systems you would have a more granular level as well (“atoms” in Atomic Design). Emulsify ships with pre-built and highly flexible atoms for links, images, forms, and lists (and much more). This allows us to jump directly into project-specific molecules.
So, what is our plan? We are going to first create a molecule for each component, making use of the atoms listed above wherever possible. Then, we will build an organism for the larger site header component. On the Drupal side, we will map our logo component to the Site Branding block, the search form to the default Drupal search form block, the menu to the Main Navigation block and the site header to the header region template. Now that we have a plan, let’s get started on our first component—the logo.
The Logo Molecule
Emulsify automatically provides us with everything we need to print a logo – see components/_patterns/01-atoms/04-images/00-image/image.twig
. Although it is an image atom, it has an optional img_url
variable that will wrap the image in a link if present. So, in this case, we don’t even have to create the logo component. We merely need a variant of the image component, which is easy to do in Pattern Lab by duplicating components/_patterns/01-atoms/04-images/00-image/image.yml
and renaming it as components/_patterns/01-atoms/04-images/00-image/image~logo.yml
(see Pattern Lab documentation).
Next, we change the variables in the image~logo.yml
as needed and add a new image_link_base_class
variable, naming it whatever we like for styling purposes. For those who are working in a new installation of Emulsify alongside this tutorial, you will notice this file already exists! Emulsify ships with a ready-made logo component. This means we can immediately jump into mapping our new logo component in Drupal.
Connecting the Logo Component to Drupal
Although you could just write static markup for the logo, let’s use the branding block in Drupal (the block that supplies the theme logo or one uploaded via the Appearance Settings page). These instructions assume you have a local Drupal development environment complete with Twig debugging enabled. Add the Site Branding block to your header region in the Drupal administrative UI to see your branding block on your page. Inspect the element to find the template file in play.
In our case there are two templates—the outer site branding block file and the inner image file. It is best to use the file that contains the most relevant information for your component. Seeing as we need variables like image alt and image src to map to our component, the most relevant file would be the image file itself. Since Emulsify uses Stable as a base theme, let’s check there first for a template file to use. Stable uses core/themes/stable/templates/field/image.html.twig
to print images, so we copy that file down to its matching directory in Emulsify creating templates/fields/image.html.twig
(this is the template for all image fields, so you may have to be more specific with this filename). Any time you add a new template file, clear the cache registry to make sure that Drupal recognizes the new file. Now the goal in component-driven development is to have markup in components that simply maps to Drupal templates, so let’s replace the default contents of the image.html.twig
file above (
<img{{ attributes }}> ) with the following:
{% include "@atoms/04-images/00-image/image.twig" with {
img_url: "/",
img_src: attributes.src,
img_alt: attributes.alt,
image_blockname: "logo",
image_link_base_class: "logo",
} %}
We’re using the Twig include
statement to use our markup from our original component and pass a mixture of static (url, BEM classes) and dynamic (img alt and src) content to the component. To figure out what Drupal variables to use for dynamic content, see first the “Available variables” section at the top of the Drupal Twig file you’re using and then use the Devel module and the kint function to debug the variables themselves. Also, if you’re new to seeing the BEM class variables (Emulsify-specific), see our recent post on why/how we use these variables (and the BEM function) to pass in BEM classes to Pattern Lab and the Drupal Attributes object. Basically, this include statement above will print out:
<a class="logo" href="/">
<img class="logo__img" src=”/themes/emulsify/logo.svg" alt="Home">
</a>
We should now see our branding block using our custom component markup! Let’s move on to the next molecule—the search form.
The Search Form Molecule
Component-driven development, particularly the division of components into controlled, separate atomic units, is not always perfect. But the beauty of Pattern Lab (and Emulsify) is that there is a lot of flexibility in how you markup a component. If the ideal approach of using a Twig function to include other smaller elements isn’t possible (or is too time consuming), simply write custom HTML for the component as needed for the situation! One area where we lean into this flexibility is in dealing with Drupal’s form markup. Let’s take a look at how you could handle the search block. First, let’s create a form molecule in Pattern Lab.
Form Wrapper
Create a directory in components/_patterns/02-molecules
entitled “search-form” with a search-form.twig file with the following contents (markup tweaked from core/themes/stable/templates/form/form.html.twig
):
<form {{ bem('search')}}>
{% if children %}
{{ children }}
{% else %}
<div class="search__item">
<input title="Enter the terms you wish to search for." size="15" maxlength="128" class="form-search">
</div>
<div class="form-actions">
<input type="submit" value="Search" class="form-item__textfield button js-form-submit form-submit">
</div>
{% endif %}
</form>
In this file (code here) we’re doing a check for the Drupal-specific variable “children” in order to pass one thing to Drupal and another to Pattern Lab. We want to make the markup as similar as possible between the two, so I’ve copied the relevant parts of the markup by inspecting the default Drupal search form in the browser. As you can see there are two classes we need on the Drupal side. The first is on the outer <form> wrapper, so we will need a matching Drupal template to inherit that. Many templates in Drupal will have suggestions by default, but the form template is a great example of one that doesn’t. However, adding a new template suggestion is a minor task, so let’s add the following code to emulsify.theme
:
/**
* Implements hook_theme_suggestions_HOOK_alter() for form templates.
*/
function emulsify_theme_suggestions_form_alter(array &$suggestions, array $variables) {
if ($variables['element']['#form_id'] == 'search_block_form') {
$suggestions[] = 'form__search_block_form';
}
}
After clearing the cache registry, you should see the new suggestion, so we can now add the file templates/form/form--search-block-form.html.twig
. In that file, let’s write:{% include "@molecules/search-form/search-form.twig" %}
The Form Element
We have only the “search__item” class left, for which we follow a similar process. Let’s create the file components/_patterns/02-molecules/search-form/_search-form-element.twig
, copying the contents from core/themes/stable/templates/form/form-element.html.twig
and making small tweaks like so:
{%
set classes = [
'js-form-item',
'search__item',
'js-form-type-' ~ type|clean_class,
'search__item--' ~ name|clean_class,
'js-form-item-' ~ name|clean_class,
title_display not in ['after', 'before'] ? 'form-no-label',
disabled == 'disabled' ? 'form-disabled',
errors ? 'form-item--error',
]
%}
{%
set description_classes = [
'description',
description_display == 'invisible' ? 'visually-hidden',
]
%}
<div {{ attributes.addClass(classes) }}>
{% if label_display in ['before', 'invisible'] %}
{{ label }}
{% endif %}
{% if prefix is not empty %}
<span class="field-prefix">{{ prefix }}</span>
{% endif %}
{% if description_display == 'before' and description.content %}
<div{{ description.attributes }}>
{{ description.content }}
</div>
{% endif %}
{{ children }}
{% if suffix is not empty %}
<span class="field-suffix">{{ suffix }}</span>
{% endif %}
{% if label_display == 'after' %}
{{ label }}
{% endif %}
{% if errors %}
<div class="form-item--error-message">
{{ errors }}
</div>
{% endif %}
{% if description_display in ['after', 'invisible'] and description.content %}
<div{{ description.attributes.addClass(description_classes) }}>
{{ description.content }}
</div>
{% endif %}
</div>
This file will not be needed in Pattern Lab, which is why we’ve used the underscore at the beginning of the name. This tells Pattern Lab to not display the file in the style guide. Now we need this markup in Drupal, so let’s add a new template suggestion in emulsify.theme
like so:
/**
* Implements hook_theme_suggestions_HOOK_alter() for form element templates.
*/
function emulsify_theme_suggestions_form_element_alter(array &$suggestions, array $variables) {
if ($variables['element']['#type'] == 'search') {
$suggestions[] = 'form_element__search_block_form';
}
}
And now let’s add the file templates/form/form-element--search-block-form.html.twig
with the following code:
{% include "@molecules/search-form/_search-form-element.twig" %}
We now have the basic pieces for styling our search form in Pattern Lab and Drupal. This was not the fastest element to theme in a component-driven way, but it is a good example of complex concepts that will help when necessary. We hope to make creating form components a little easier in future releases of Emulsify, similar to what we’ve done in v2 with menus. And speaking of menus…
The Main Menu
In Emulsify 2, we have made it a bit easier to work with another complex piece of Twig in Drupal 8, which is the menu system. The files that do the heavy-lifting here are components/_patterns/02-molecules/menus/_menu.twig and components/_patterns/02-molecules/menus/_menu-item.twig (included in the first file). We also already have an example of a main menu component in the directory
themes/emulsify/components/_patterns/02-molecules/menus/main-menu
which is already connected in the Drupal template
templates/navigation/menu--main.html.twig
Obviously, you can use this as-is or tweak the code to fit your situation, but let’s break down the key pieces which could help you define your own menu.
Menu Markup
Ignoring the code for the menu toggle inside the file, the key piece from themes/emulsify/components/_patterns/02-molecules/menus/main-menu/main-menu.twig
is the include statement:
<nav id="main-nav" class="main-nav">
{% include "@molecules/menus/_menu.twig" with {
menu_class: 'main-menu'
} %}
</nav>
This will use all the code from the original heavy-lifting files while passing in the class we need for styling. For an example of how to stub out component data for Pattern Lab, see components/_patterns/02-molecules/menus/main-menu/main-menu.yml
. This component also shows you how you can have your styling and javascript live alongside your component markup in the same directory. Finally, you can see a more simple example of using a menu like this in the components/_patterns/02-molecules/menus/inline-menu
component. For now, let’s move on to placing our components into a header organism.
The Header Organism
Now that we have our three molecule components built, let’s create a wrapper component for our site header. Emulsify ships with an empty component for this at components/_patterns/03-organisms/site/site-header
. In our usage we want to change the markup in components/_patterns/03-organisms/site/site-header/site-header.twig
to:
<header class="header">
<div class="header__logo">
{% block logo %}
{% include "@atoms/04-images/00-image/image.twig" %}
{% endblock %}
</div>
<div class="header__search">
{% block search %}
{% include "@molecules/search-form/search-form.twig" %}
{% endblock %}
</div>
<div class="header__menu">
{% block menu %}
{% include "@molecules/menus/main-menu/main-menu.twig" %}
{% endblock %}
</div>
</header>
Notice the use of Twig blocks. These will help us provide default data for Pattern Lab while giving us the flexibility to replace those with our component templates on the Drupal side. To populate the default data for Pattern Lab, simply create components/_patterns/03-organisms/site/site-header/site-header.yml
and copy over the data from components/_patterns/01-atoms/04-images/00-image/image~logo.yml
and components/_patterns/02-molecules/menus/main-menu/main-menu.yml
. You should now see your component printed in Pattern Lab.
Header in Drupal
To print the header organism in Drupal, let’s work with the templates/layout/region--header.html.twig
file, replacing the default contents with:
{% extends "@organisms/site/site-header/site-header.twig" %}
{% block logo %}
{{ elements.emulsify_branding }}
{% endblock %}
{% block search %}
{{ elements.emulsify_search }}
{% endblock %}
{% block menu %}
{{ elements.emulsify_main_menu }}
{% endblock %}
Here, we’re using the Twig extends
statement to be able to use the Twig blocks we created in the component. You can also use the more robust embed
statement when you need to pass variables like so:
{% embed "@organisms/site/site-header/site-header.twig" with {
variable: "something",
} %}
{% block logo %}
{{ elements.emulsify_branding }}
{% endblock %}
{% block search %}
{{ elements.emulsify_search }}
{% endblock %}
{% block menu %}
{{ elements.emulsify_main_menu }}
{% endblock %}
{% endembed %}
For our purposes, we can simply use the extends
statement. You’ll notice that we are using the elements
variable. This variable is currently not listed in the Stable region template at the top, but is extremely useful in printing the blocks that are currently in that region. Finally, if you’ve added the file, be sure and clear the cache registry—otherwise, you should now see your full header in Drupal.
Final Thoughts
Component-driven development is not without trials, but I hope we have touched on some of the more difficult ones in this article to speed you on your journey. If you would like to view the branch of Emulsify where we built this site header component, you can see that here. Feel free to sift through and reverse-engineer the code to figure out how to build your own component-driven Drupal project!
This fifth episode concludes our five-part video-blog series for Emulsify 2.x. Thanks for following our Emulsify 2.x tutorials. Miss a post? Read the full series here.
Pt 1: Installing Emulsify | Pt 2: Creating your Emulsify 2.0 Starter Kit with Drush | Pt 3: BEM Twig Function | Pt 4: DRY Twig Approach
Just need the videos? Watch them all on our channel.
The post Emulsify 2: Building a Full Site Header in Drupal appeared first on Four Kitchens.