2015 May 22

How our new website came to be on D8

Looking back on past major releases of Drupal, one can surely sense the zeitgeist of web technologies in use for that given period.

They are virtual time-capsules of best-practices and standards.

Since the time between major Drupal versions is quite large and web technologies move so fast, we can understand the reform that each new major version of Drupal demonstrates. Every time we're on the eve of a new release it feels like a breath of fresh air, a leap forward to the present-day hotness that developers have access to.

In the case of Drupal 8, it's no exception.

There are a lot of changes under the hood and certain phrases come to mind to describe working with it such as "next-gen", "modern CMS", "PHP framework using actually recognizable code patterns (versus proprietary)", "completely different CMS than what we're used to, using Drupal's name". It kind of just keeps going on like that, but you get the idea (it's different, but exciting).

Now that version 8 is in a good place beta-wise, we wanted to start playing around with it. We were in the midst of re-designing our company site so, why not do it in Drupal 8?

There are many good things about approaching it this way:

  1. We a get low-pressure introduction to it on an actual project. 
  2. It's a good way to take the fear out of getting started with new technology.
  3. Our site isn't overly complicated, so in this case, a stock Drupal 8 install would probably be a perfect fit. Drupal 8 is extremely powerful out of the box compared to earlier versions and using just core and a custom theme will work.
  4. It turns out others are already doing this as well.     

Once the idea came to be, we started doing research. Those already getting their feet wet with D8 and blogging about their experience were really helpful, so we wanted to make this post about certain highlights from our "boots on the ground" experience. Seeing what others were actively working with D8, in real production-ready environments provided us that extra assurance that "yeah, I can use this too 'cuz someone has already done it". However, it is still beta software, and we felt that highlighting some of our own experience might help others as well.

Although Drupal itself has a learning curve, the "tribal knowledge" or rather "Drupalisms" of the past have melted away. Drupal 8 now uses other well known libraries instead of relying on an internally managed codebase to do common tasks. For non-Drupal PHP developers this means they should get up to speed with Drupal 8 faster than they would have done with Drupal 7.

You can read more about this transition here.

The Theme

Our workflow usually starts with the design mockups to create a base HTML structure (or UI inventory) document. From there we can start bringing that into Drupal as a theme. You can have common sections copy/pasted over and just fill in the dynamic bits to make it work. This works well for things like:

  • header/footer -> html.html.twig
  • any middle page content (sidebars, content) -> page.html.twig
  • article area for single pages -> node.html.twig
  • any specific menus, field styles -> menu.html.twig or field.html.twig

D8 has a tonne more template overrides than ever before. For the most part, the usual suspects from v7 are available in 8 as well (the one's mentioned above). The naming has just changed from .tpl.php to .html.twig. Just copying from core into your theme/clear cache and you're good, same as before.

Other ingredients needed for a theme:

  • yourtheme.theme - This is synonymous with the template.php file from 7. You throw your theme functions (like preprocess page, preprocess node, etc) in here. We usually create a lib dir in our theme with a bunch of includes for each broken out section. For example, we may have a file with just preprocess page in it and that will be another include within the yourtheme.theme file. We borrowed this idea from the roots theme for Wordpress. This really helps with code organization, rather than having a 1000 line file with a tonne of functions in it.
  • yourtheme.libraries.yml - This is where you'll put your JS/CSS assets. You can read more about that here.
  • yourtheme.info.yml - This is basically the same as the .info file from 7, just YAMLfied and with more options.
  • templates dir - We use this to stash all our templates. Inside, we'll also have more wrapper directories for more breakdown such as node (for all node.html.twig) and page (for all page.html.twig). Drupal will find everything regardless of the directory structure, so go ahead and carve it up! You don't have to have this, but we strongly recommend it as it makes your theme folder more organized.

The INFO file, here's ours:


name: ind
type: theme
description: 'A custom theme developed by industrial'
package: Core
core: '8.x'
stylesheets-remove:
  # - core/modules/system/css/system.theme.css
ckeditor_stylesheets:
  - styles/components/elements.css
  - styles/components/captions.css
  - styles/components/content.css
  - styles/components/table.css
  - styles/main.css
libraries:
  # - roots/jquery
  - roots/global-styling
regions:
  header: Header
  message_output: 'Messages'
  main_menu: 'Main menu'
  breadcrumb: Breadcrumb
  content: Content
  below_content: Below Content
  sidebar: 'Sidebar'
  footer: 'Footer'
  menu_bottom: 'Menu Bottom'

Some items to highlight:

  • Ckeditor_stylesheets is something bartik uses, and we haven't seen any documentation on it, but it's really useful for adding styles into your ckeditor (a pain point in the past).
  • You'll notice that we have # - roots/jquery commented out. We were toying with the ability to pull in remote assets and were using this as an example. See below for how it's used in the libraries.yml file.

The libraries.yml file, here's ours:

global-styling:
  version: VERSION
  css:
    theme:
      styles/main.css: {}
      styles/components/node-preview.css: {}
      styles/components/messages.css: {}
  js:
    js/plugins.js: {}
    js/main.js: {}
  dependencies:
    - core/jquery

# for example of remote resource
# jquery:
#   remote: //ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js
#   license:
#     name: GNU-GPL-2.0-or-later
#   js:
#     //ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js:

For that jQuery to work, it would need to be at the first level in the yml file. This will come in handy for those of you using Google font CDN's, etc. Also, if you want to use jQuery, you'll have to make it a dependency of your theme. By default Drupal doesn't include it.

Preprocess Functions

In the past we've used a trick to get Wordpress style custom page templates. By default, Drupal gives you node type templates. However, gaining one for the page-level template can give you more flexibility. It involves creating a new vocabulary with terms representing each page suggestion. Then in the yourtheme_theme_suggestions_page_alter function (in 7 this used to use theme_hook_suggestions array inside hook_preprocess_page), it looks like the following:

/**
 * Add in template suggestions based on content type
 */
function roots_theme_suggestions_page_alter(&$suggestions, $variables) {
  $node = \Drupal::request()->attributes->get('node');
  if ($node) {
    if ($node->getType()) {
      $suggestions[] = 'page__' . $node->getType();
    }

    if($node->hasField('field_page_template')){
    $custom_page_template = $node->get('field_page_template')->getValue();
      if(isset($custom_page_template[0]['target_id'])){
        $term = \Drupal\taxonomy\Entity\Term::load($custom_page_template[0]['target_id'])->getname();
        $term = 'page__' . preg_replace("/[^a-zA-Z0-9s]/", "_", strtolower($term));
        $suggestions[] = $term;
      }
    }
  }
}

The page structure will look like this, see the debugging comments included:

template suggestions

For more information see hook_theme_suggestions.

The other thing that we've done in the past is access the node vars within page level templates. This is useful for, say, putting node fields into sidebars or on different parts of the page outside of what node.html.twig provides.

This appears to be more difficult with D8 as the twig engine doesn't allow and PHP logic, although the node object does get passed into it. We mentioned above how we used template suggestions to make new page.html.twig versions. Well, we've resorted to putting a lot of setup logic for variables into the preprocess_page function. There might be a better way to provide the node fields to the page template but, for now, that's how we've done it.

Setting up for local development

  1. In sites\default\settings.php at the bottom, uncomment the lines dealing with settings.local.php.
  2. Then copy sites/example.settings.local.php to sites/default/settings.local.php. This will do things like kill JS/CSS caching/aggregation and disable the render cache (this includes the page cache) so you can actually work on the site and see the changes.
  3. Also, in sites/default/services.yml, you'll want to toggle debug: false to debug: true. This will make the output of the source verbose enough for you to see which templates are being used, etc. For more information, check out this.

Custom Block Modules

We've made a few custom modules for this site that all produce dynamic blocks of content. This was done for a couple of reasons. One, it's dead easy, and two it fell into place with how the rest of the site was already being built.

Being a very designed site, the use of blocks was really important rather than relying on template logic or a lot of fields that are only being used in one place. There was a need for some sections to have content driven by the site, so it was a no brainer to create these extra blocks to literally fill in the gaps. 

A few examples include the sitemap, which takes the footer and main menus and turns it into one full menu. The case studies are pulled from a content type of the same name, and a social media block that can be thrown into the sidebar on any page to show the share links for facebook, twitter and linkedin.

Here is an example of the social block:

directory structure for the social block module

You'll notice that we separate the contrib modules from the custom ones. This is another great habit to get into as you can quickly see what you've made vs downloaded.

This is a built in drupal convention that we used back in D7 as well. If you have your folder structure setup like this and use drush to download modules, it'll know you've done this and proceed to put the contrib module in the contrib dir.

modules\custom\social_sharing_block\social_sharing_block.info.yml

name: Social Sharing Block
description: Block that outputs sharing options based on current page.
core: 8.x
package: Custom
type: module

modules\custom\social_sharing_block\social_sharing_block.module
This is intentionally left empty.
 
modules\custom\social_sharing_block\src\Plugin\Block\social_sharing_block.php

<?php
/**
 * @file
 * Contains \Drupal\social_sharing_block\Plugin\Block\YourBlockName.
 */
namespace Drupal\social_sharing_block\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
 * Provides social sharing block.
 *
 * @Block(
 *   id = "social_sharing_block",
 *   admin_label = @Translation("Social Sharing Block"),
 *   category = @Translation("Custom")
 * )
 */
class social_sharing_block extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    $node = \Drupal::request()->attributes->get('node');
    if ($node) {
      $title = $node->getTitle();
      $summary = $node->get('body')->summary != '' ? htmlspecialchars($node->get('body')->summary,ENT_QUOTES) : '';
    }

    $current_url = \Drupal::request()->getHttpHost().\Drupal::request()->getRequestUri();
    $return = "<ul class='social'>
                 <li><a target=_blank href='https://twitter.com/intent/tweet?url=http://$current_url' class='twitter'>Share on Twitter</a></li>
                 <li><a target=_blank href='http://www.linkedin.com/shareArticle?mini=true&url=http://$current_url&title=$title&summary=$summary' class='linkedin'>Share on LinkedIn</a></li>
                 <li><a target=_blank href='https://www.facebook.com/sharer/sharer.php?u=http://$current_url&t=$title' class='facebook'>Share on Facebook</a></li>
               </ul>";
    return array('#markup' => $return);
  }
}

Configuration Management

One of the biggest reasons we've been looking forward to Drupal 8 is the new CM initiative.

Basically, instead of using features, strongarm or hook_install within custom modules to ferry around code changes between development -> stage -> production, you can export single or entire configurations from the database into YAML files that you can version in source control.

The active store is in the database by default, but it turns out you can also use the filesystem for storing the active configuration. It just seems more ominous as it's not the default method and you have to set it up before installing Drupal. This is how you export your configuration using drush:

drush config-export
git add -f sites/default/files/config_wNOLcmycPFZCrXJ9wis9dCdSR4lpYILdBsFxSWuK5Hzhcr-irILQ0u25dfasd9sdfsadWaUDwMg
git commit -m 'Exporting configuration to code.'
git push origin master
drush config-import

Note that this is only configuration export, as the name suggests. If your configuration relies on content being on the destination site, that'll have to be brought over with some other means.

If you don't have drush installed, there's also a backend administrative area that uses tar files that get imported/exported for full configuration transfers, or YAML output for one off jobs.