BEM/OOCSS in Drupal – Why BEM? – Identifying



BEM/OOCSS in Drupal – Why BEM? – Identifying

0 1


bem-in-drupal-presentation


On Github bradwade / bem-in-drupal-presentation

BEM/OOCSS in Drupal

Created by Brad Wade / bwade@phase2technology.com and Brian McMurray / bmcmurray@phase2technology.com

What is BEM?

BEM (Block, Element, Modifier) is a methodology for determining a website's nomenclature.

It helps you create ideal, semantic markup and class names that provide designers, frontend, and backend programmers the same terms to communicate with one another.

It helps achieve more organized, reusable, and maintainable code. It provides front end developers with rich, specific information about what they are styling.

Where did it come from?

BEM was developed by a group of Russian programmers working for Yandex company.

bem.info

Similar to SMACCS or other OOCSS.

Why BEM?

Cure for Divitis You are able to clear out many divs that are there "in case you need them". It is easy to complain about how the markup and class names in Drupal are bloated -- but harder to suggested a good, flexible alternative, solution.

Get ready for Drupal 8 It looks like Drupal 8 is adopting a very similar CSS Architecture approach

CSS architecture (for Drupal 8)https://drupal.org/node/1887918

Reusable components! Styling of components is not based on context/location. Therefore moving a component between pages, content types, from the sidebar to footer or repeating it multiple times on a page should not affect the styling.

Why BEM?

Maintainable

Readable

Predictable

Team scalability

body.article-node #sidebar .block-3 h3 {
  padding-left: 15px;
}
.newsletter-cta__heading {
  padding-left: 15px;
}

Identifying

  • Blocks
  • Elements
  • Modifiers

Naming Blocks

Block names must be globally unique.

Use a single dash for multi-word block names.

Keep it Semantic! We don't need to add styling information into classes. We can maintain seperation of styling and markup. Don't name things based on styling or position/context.

Bad: blue-search-bar, sidebar-search, footer-links.

Naming is hard. Don't get bogged down. Try to name quickly and move on.

/* example block/component names */
.logo {}
.pager {}
.tab-block {}
.main-menu {}

Pager example

<<    <    3    >    >>
<ul class="pager">
  <li>First</li>
  <li>Previous</li>
  <li>3</li>
  <li>Next</li>
  <li>Last</li>
</ul>

Naming Elements

Element names only need to be unique within the component.

Use a single dash for multi-word element names.

Add the element name to the end of the block name and use a double underscore to separate the two.

Pager example

<<    <    3    >    >>
<ul class="pager">
  <li class="pager__control">First</li>
  <li class="pager__control">Previous</li>
  <li class="pager__current-page>3</li>
  <li class="pager__control">Next</li>
  <li class="pager__control">Last</li>
</ul>

Naming Modifiers

Use a single dash for multi-word element names.

Add the modifier name to the end of the block or element use a double dash to separate.

Modifiers can have a type and value for example: size-small size-large position-first position-last depth-2 depth-3 state-active priority-normal priority-important priority-critical status-active

<ul class="pager">
  <li class="pager__control pager__control--first">First</li>
  <li class="pager__control pager__control--previous">Previous</li>
  <li class="pager__current-page>3</li>
  <li class="pager__control pager__control--next">Next</li>
  <li class="pager__control pager__control--last">Last</li>
</ul>
.pager {
  @include horizontal-list();
}

.pager__current-page {
  font-weight: bold;
}

.pager__control {
  @include hide-text();
  display: block;
  background: url("images/sprite.png") 0 0;
  width: 20px;
  height: 20px;
  margin-right:5px;
  padding: 0px;
}

.pager__control--first {
  background-position: 0 0px;
}

.pager__control--previous {
  background-position: 0 -30px;
}

.pager__control--next {
    background-position: 0 -60px;
}

.pager__control--last {
    background-position: 0 -90px;
}

Unsetting Core CSS

Core CSS uses HTML elements with to great of a specificity. So it would clobber our BEM-y CSS.

/* Example from system.menus.css */
ul.menu li {
  margin: 0 0 0 0.5em; /* LTR */
}
ul li.expanded {
  list-style-image: url(../../misc/menu-expanded.png);
  list-style-type: circle;
}

Unsetting Core CSS

Refer to non-existant files in your theme's .info file.

name = Theme name
description = My sweet theme
core = 7.x

; STYLESHEETS
;-------------------------------------------------------------------------------
stylesheets[all][] = css/style.css
; Note: removes system so we don't have to override styles.
stylesheets[all][] = system.menus.css
stylesheets[all][] = system.theme.css

Implementing BEM in Drupal7

  • Idealism vs Realism
  • Approaches

Idealism vs Realism

Drupal *gasp* is an opinionated system and isn't going to make everything easy for you when your ideals run counter to its opinions.

Idealism vs Realism

The danger of forcing your idealism on Drupal is creating a system that becomes fragile to upgrades.

If you override every core theme function, you're in for a bad time.

If you can get your BEM classes in place, you can ignore some of Drupal's divitis. Focus instead on keeping divitis out of your CSS.

Idealism vs Realism

  • Choose your battles wisely.
  • Focus on the important output.
  • Document your code!

Custom Theming Approaches

  • Template files
  • Views
  • Panels
  • template.php

Implement BEM in: custom template files

  • Exactly what it sounds like, make custom .tpl.php files in your theme and add your BEM classes as you'd like.

  • Can lead to lots of template files that are difficult to organize, manage, and not very flexible.

  • In Node template files, you can add BEM classes around field output.

  • To add BEM classes into field output, you'll have to add field templates or use other methods (like template preprocessing)

Implement BEM in: Views

Views gives you lots of options to add custom CSS classes to its output.

Many of those options are buried in advanced options or settings.

Implement BEM in: Views

Implement BEM in: Views

Implement BEM in: Views

Implement BEM in: Views

Implement BEM in: Views

Implement BEM in: Views

Implement BEM in: Panels

  • It's like node templating, only with a user interface

  • You can add classes to the <body> tag

  • You can customize your layouts (but should consider leaving out BEM classes, layouts are meant to be more abstract and general use)

  • You can add a class to any or every Pane you add as content

Implement BEM in: Panels

Implement BEM in: Panels

Implement BEM in: Panels

Implement BEM in: Panels

Implement BEM in: template.php

  • template.php is the heart of your theme. Using process_ and preprocess_ functions will help you massage Drupal's output and can compliment the previous approaches.

  • Implement theme_ function overrides to further massage and alter Drupal's default HTML output. This can be useful for modifying output that doesn't run through the other methods, like menu trees, pagers, etc.

Implement BEM in: template.php

Add BEM classes to your nodes in YOURTHEME_preprocess_node(&$variables).

<?php
$variables['classes_array'][] =
'node--' . $variables['type'] . '--' . $variables['view_mode'] .
' node--' . $variables['view_mode'];

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

The Problem: Drupal outputs menus all over using menu_tree() and theme('menu_tree') but the HTML output isn't BEM-y.

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

Solution: Implement theme_menu_tree.

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

Let's see what we have here...

Drupal's built-in theme_menu_tree is incredibly basic.

<?php
function theme_menu_tree($variables) {
  return '<ul class="menu">' . $variables['tree'] . '</ul>';
}

The variable tree just contains rendered HTML of the menu tree.

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

The Problem Gets Harder: Our ideal menu markup includes the depth and the menu's name as part of the BEM classes, but those aren't in the $variables available to us in theme_menu_tree

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

Our ideal markup:

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

First, we need the menu name and depth available to us, but they aren't in $variables. In fact, they were available but were removed in template_preprocess_menu_tree().

<?php
function template_preprocess_menu_tree(&$variables) {
  $variables['tree'] = $variables['tree']['#children'];
}

To fix this, we have to alter the theme registry.

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

We alter the theme registry and prepend a new preprocess function before template_preprocess_menu_tree().

<?php
/**
 * Implements hook_theme_registry_alter().
 *
 * Alters the theme registry to insert our preprocess function before
 * menu_tree's default preprocess function.
 */
function YOURTHEME_theme_registry_alter(&$theme_registry) {
  // Insert a preprocess function before template_preprocess_menu_tree.
  array_unshift(
    $theme_registry['menu_tree']['preprocess functions'],
    'YOURTHEME_preprocess_bem_menu_tree'
  );
}

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

The preprocess function grabs the menu_name and the depth from the original tree and saves them as new $variables for use in the theme implementation.

<?php
/**
 * Custom preprpocess function to allow us to capture some variables in menu_tree
 * before `template_preprocess_menu_tree` destroys them.
 */
function YOURTHEME_preprocess_bem_menu_tree(&$variables) {
  $tree = $variables['tree'];
  $menu_name = FALSE;
  $depth = FALSE;

  foreach ($tree as $menu_item) {
    if (is_array($menu_item) && array_key_exists('#original_link', $menu_item)) {
      $menu_name = $menu_item['#original_link']['menu_name'];
      $depth = $menu_item['#original_link']['depth'];
    }
  }

  if (!empty($menu_name)) {
    $variables['menu_name'] = $menu_name;
  }
  if (!empty($depth)) {
    $variables['depth'] = $depth;
  }
}

Implement BEM in: template.php

A more in-depth example: Making Drupal output BEM-y menu trees.

Simple updates using the newly available variables allows the inclusion of the menu_name and depth.

<?php
/**
 * Implements theme_menu_tree().
 *
 * @details Adds a unique class to each UL, using variables we
 *          have captured with
 *          `YOURTHEME_preprpocess_bem_menu_tree()`.
 */
function YOURTHEME_menu_tree($variables) {
  $ul = array(
    '#theme' => 'html_tag',
    '#tag' => 'ul',
    '#value' => $variables['tree'],
    '#attributes' => array(
      'class' => array('menu'),
    ),
  );

  if (!empty($variables['menu_name'])) {
    $ul['#attributes']['class'][] = 'menu-'. $variables['menu_name'];
    if (!empty($variables['depth']) && $variables['depth'] > 1) {
      $ul['#attributes']['class'][] =
      'menu-'. $variables['menu_name'] .'--tier'. $variables['depth'];
    }
  }
  return drupal_render($ul);
}

THE END

BEM in Drupal

Created by Brad Wade / bwade@phase2technology.com and Brian McMurray / bmcmurray@phase2technology.com